2 Commits
Author SHA1 Message Date
blandClaude Opus 5 133152742f Mål grunnmuren over bakken fra overkant mur
Release enok / bygg (release) Successful in 1m13s
test / test (push) Successful in 1m20s
Høyden ble målt fra gulvet i etasjen over, så dekkets egen tykkelse ble regnet som mur over
bakken. Nå måles den fra overkant av selve muren, som geometrien gir den. For Nordhusstykket
5 går høyden fra 1,11–1,50 m til 0,76–1,24 m, i snitt 0,98 m.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015GeH3y8fwTaTudaD88P57o
2026-09-21 15:48:52 +02:00
blandClaude Opus 5 959db648a9 Skill dekket over kjelleren fra dekket på bakken
test / test (push) Successful in 1m17s
Release enok / bygg (release) Successful in 1m13s
Et dekke like under gulvet i første etasje er himlingen over kjelleren, også når det heter
noe med «kjellergulv»; det er et vanlig navn på dekket over kjelleren. Bare navn som sier
«på grunn» eller «on grade» gir dekke mot grunnen, ellers avgjør høyden: nederste dekke
ligger mot grunnen, resten er etasjeskillere inne i bygningen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015GeH3y8fwTaTudaD88P57o
2026-09-21 15:43:03 +02:00
4 changed files with 35 additions and 16 deletions
Generated
+1 -1
View File
@@ -222,7 +222,7 @@ dependencies = [
[[package]] [[package]]
name = "enok" name = "enok"
version = "0.4.4" version = "0.4.6"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"approx", "approx",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "enok" name = "enok"
version = "0.4.4" version = "0.4.6"
edition = "2021" edition = "2021"
description = "Årlig energiberegning og energirapport for boliger etter NS 3031 (timesmetode, ISO 52016-1)" description = "Årlig energiberegning og energirapport for boliger etter NS 3031 (timesmetode, ISO 52016-1)"
license = "MIT" license = "MIT"
+16 -14
View File
@@ -186,6 +186,7 @@ pub fn fra_tekst_med(tekst: &str, standardnavn: &str, valg: &Importvalg) -> Resu
areal: flate.map(|f| f.areal).unwrap_or(0.0), areal: flate.map(|f| f.areal).unwrap_or(0.0),
normal: flate.map(|f| f.normal), normal: flate.map(|f| f.normal),
z: plass.map(|p| p.2).unwrap_or(0.0) + flate.map(|f| f.hoyde).unwrap_or(0.0), z: plass.map(|p| p.2).unwrap_or(0.0) + flate.map(|f| f.hoyde).unwrap_or(0.0),
topp: plass.map(|p| p.2).unwrap_or(0.0) + flate.map(|f| f.topp).unwrap_or(0.0),
}); });
} }
@@ -363,12 +364,13 @@ pub fn fra_tekst_med(tekst: &str, standardnavn: &str, valg: &Importvalg) -> Resu
} }
// Terrengmodellen sier hvor høyt bakken ligger akkurat der denne veggen står, så en // Terrengmodellen sier hvor høyt bakken ligger akkurat der denne veggen står, så en
// skrånende tomt gir forskjellig høyde over bakken på hver side av huset. // skrånende tomt gir forskjellig høyde over bakken på hver side av huset.
// Høyden måles fra overkant mur, ikke fra gulvet over: dekket selv står ikke i bakken.
let over_bakken = d let over_bakken = d
.plass .plass
.and_then(|(x, y, _, _, _)| terreng_ved(&terrengmodell, x, y)) .and_then(|(x, y, _, _, _)| terreng_ved(&terrengmodell, x, y))
.map(|z| (etasjekontekst.forste_over_hoyde.unwrap_or(0.0) - z).clamp(0.0, kjellerdybde)) .map(|z| (d.topp - z).clamp(0.0, kjellerdybde))
.unwrap_or(kjeller_over_terreng); .unwrap_or(kjeller_over_terreng);
if i_kjeller && mot == Mot::Grunn { if i_kjeller && mot == Mot::Grunn && typ == FlateType::Vegg {
grunnmurshoyder.push(over_bakken); grunnmurshoyder.push(over_bakken);
} }
let mal = if i_kjeller { let mal = if i_kjeller {
@@ -1316,6 +1318,8 @@ struct Del {
normal: Option<[f64; 3]>, normal: Option<[f64; 3]>,
/// Delens høyde over byggets nullpunkt, av geometrien, m. /// Delens høyde over byggets nullpunkt, av geometrien, m.
z: f64, z: f64,
/// Overkanten av delen over byggets nullpunkt, m. For en kjellervegg er det overkant mur.
topp: f64,
} }
/// Oversetter en fritekstverdi fra modellen til en bygningskategori. /// Oversetter en fritekstverdi fra modellen til en bygningskategori.
@@ -1504,20 +1508,15 @@ fn klassifiser(
if d.ytre == Some(true) { if d.ytre == Some(true) {
return Some((FlateType::Gulv, Mot::Ute)); return Some((FlateType::Gulv, Mot::Ute));
} }
// Mot grunnen i det nederste planet, eller når navnet sier det. Høyden av geometrien // Høyden av geometrien avgjør, siden etasjetilknytningen i en eksport kan være feil.
// avgjør, siden etasjetilknytningen i en eksport kan være feil. // Et dekke som ligger på bakken sier det i navnet. Ellers er et dekke like under
let paa_grunnen = [ // gulvet i første etasje himlingen over kjelleren, og det nederste dekket ligger mot
"kjellergulv", // grunnen. «Kjellergulv» er ikke nok til å si at dekket ligger på bakken: dekket over
"on grade", // kjelleren bærer ofte det samme navnet.
"på grunn", let paa_grunnen = ["on grade", "på grunn", "paa grunn", "såle", "sale"];
"paa grunn", if paa_grunnen.iter().any(|o| navn.contains(o)) {
"såle",
"sale",
];
if (d.z - laveste).abs() < 0.5 || paa_grunnen.iter().any(|o| navn.contains(o)) {
return Some((FlateType::Gulv, Mot::Grunn)); return Some((FlateType::Gulv, Mot::Grunn));
} }
// Dekket i laveste etasje over en kjeller er grensen mot den uoppvarmede sonen.
if e.kjeller.is_some() { if e.kjeller.is_some() {
let ved_forste_over = e let ved_forste_over = e
.forste_over_hoyde .forste_over_hoyde
@@ -1527,6 +1526,9 @@ fn klassifiser(
return Some((FlateType::Gulv, Mot::Uoppvarmet)); return Some((FlateType::Gulv, Mot::Uoppvarmet));
} }
} }
if (d.z - laveste).abs() < 0.5 {
return Some((FlateType::Gulv, Mot::Grunn));
}
// Dekker lenger opp er etasjeskillere inne i bygningen. // Dekker lenger opp er etasjeskillere inne i bygningen.
return None; return None;
} }
+17
View File
@@ -30,6 +30,8 @@ pub struct Hovedflate {
/// Midlere høyde over delens eget nullpunkt, m. Sier hvilken etasje delen ligger i når /// Midlere høyde over delens eget nullpunkt, m. Sier hvilken etasje delen ligger i når
/// plasseringen i filen ikke gjør det. /// plasseringen i filen ikke gjør det.
pub hoyde: f64, pub hoyde: f64,
/// Øverste punkt over delens eget nullpunkt, m. For en kjellervegg er det overkant mur.
pub topp: f64,
} }
impl Nett { impl Nett {
@@ -103,11 +105,18 @@ impl Nett {
} else { } else {
self.punkter.iter().map(|p| p[2]).sum::<f64>() / self.punkter.len() as f64 * skala self.punkter.iter().map(|p| p[2]).sum::<f64>() / self.punkter.len() as f64 * skala
}; };
let topp = self
.punkter
.iter()
.map(|p| p[2])
.fold(f64::NEG_INFINITY, f64::max)
* skala;
Some(Hovedflate { Some(Hovedflate {
areal: sum / 2.0, areal: sum / 2.0,
normal, normal,
plan: self.utstrekning(normal, skala), plan: self.utstrekning(normal, skala),
hoyde, hoyde,
topp: if topp.is_finite() { topp } else { 0.0 },
}) })
} }
@@ -189,9 +198,14 @@ pub fn samlet_hovedflate(deler: &[Nett], skala: f64) -> Option<Hovedflate> {
} else { } else {
grupper[0].hoyde grupper[0].hoyde
}; };
let topp = grupper
.iter()
.map(|g| g.topp)
.fold(f64::NEG_INFINITY, f64::max);
Some(Hovedflate { Some(Hovedflate {
areal, areal,
hoyde, hoyde,
topp,
..grupper[0] ..grupper[0]
}) })
} }
@@ -562,6 +576,9 @@ mod tester {
let h = n.hovedflate(1.0).unwrap(); let h = n.hovedflate(1.0).unwrap();
assert!((h.areal - 12.0).abs() < 1e-9, "areal ble {}", h.areal); assert!((h.areal - 12.0).abs() < 1e-9, "areal ble {}", h.areal);
assert!(h.normal[2].abs() > 0.99, "normal ble {:?}", h.normal); assert!(h.normal[2].abs() > 0.99, "normal ble {:?}", h.normal);
// Midlere høyde er halve tykkelsen, og overkanten er tykkelsen.
assert!((h.hoyde - z / 2.0).abs() < 1e-9, "høyde ble {}", h.hoyde);
assert!((h.topp - z).abs() < 1e-9, "topp ble {}", h.topp);
} }
/// Et hull i flaten skal trekkes fra, siden hullet mangler trekanter. /// Et hull i flaten skal trekkes fra, siden hullet mangler trekanter.