Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
133152742f
|
||
|
|
959db648a9
|
||
|
|
3dff2dc273
|
||
|
|
b19162d9a7
|
Generated
+1
-1
@@ -222,7 +222,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "enok"
|
name = "enok"
|
||||||
version = "0.4.2"
|
version = "0.4.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"approx",
|
"approx",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "enok"
|
name = "enok"
|
||||||
version = "0.4.2"
|
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"
|
||||||
|
|||||||
+96
-15
@@ -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),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,6 +287,7 @@ pub fn fra_tekst_med(tekst: &str, standardnavn: &str, valg: &Importvalg) -> Resu
|
|||||||
// Den laveste etasjen over terreng, der etasjeskilleren mot kjelleren ligger.
|
// Den laveste etasjen over terreng, der etasjeskilleren mot kjelleren ligger.
|
||||||
let forste_over = etasjer.iter().position(|e| !e.under_terreng);
|
let forste_over = etasjer.iter().position(|e| !e.under_terreng);
|
||||||
// Terrenghøyden på IfcBuilding sier hvor mye av grunnmuren som står over bakken.
|
// Terrenghøyden på IfcBuilding sier hvor mye av grunnmuren som står over bakken.
|
||||||
|
let terrengmodell = terrengpunkter(&fil, skala);
|
||||||
let terreng = terrenghoyder(&fil, skala);
|
let terreng = terrenghoyder(&fil, skala);
|
||||||
// Ligger terrengmodellen over gulvet i første etasje, er den ikke lagt i bakkenivå, og da
|
// Ligger terrengmodellen over gulvet i første etasje, er den ikke lagt i bakkenivå, og da
|
||||||
// sier den ingenting om hvor mye av grunnmuren som er nedgravd.
|
// sier den ingenting om hvor mye av grunnmuren som er nedgravd.
|
||||||
@@ -308,6 +310,7 @@ pub fn fra_tekst_med(tekst: &str, standardnavn: &str, valg: &Importvalg) -> Resu
|
|||||||
let mut utelatt: Vec<(String, f64, Option<f64>)> = Vec::new();
|
let mut utelatt: Vec<(String, f64, Option<f64>)> = Vec::new();
|
||||||
let mut uten_uverdi: Vec<String> = Vec::new();
|
let mut uten_uverdi: Vec<String> = Vec::new();
|
||||||
let mut uten_geometri = 0usize;
|
let mut uten_geometri = 0usize;
|
||||||
|
let mut grunnmurshoyder: Vec<f64> = Vec::new();
|
||||||
let mut asimut_for_vegg: HashMap<u64, f64> = HashMap::new();
|
let mut asimut_for_vegg: HashMap<u64, f64> = HashMap::new();
|
||||||
|
|
||||||
// Laveste etasje og laveste gulvnivå skiller dekke mot grunn fra etasjeskillere.
|
// Laveste etasje og laveste gulvnivå skiller dekke mot grunn fra etasjeskillere.
|
||||||
@@ -359,6 +362,17 @@ pub fn fra_tekst_med(tekst: &str, standardnavn: &str, valg: &Importvalg) -> Resu
|
|||||||
if d.u_verdi.is_none() {
|
if d.u_verdi.is_none() {
|
||||||
uten_uverdi.push(d.navn.clone());
|
uten_uverdi.push(d.navn.clone());
|
||||||
}
|
}
|
||||||
|
// 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.
|
||||||
|
// Høyden måles fra overkant mur, ikke fra gulvet over: dekket selv står ikke i bakken.
|
||||||
|
let over_bakken = d
|
||||||
|
.plass
|
||||||
|
.and_then(|(x, y, _, _, _)| terreng_ved(&terrengmodell, x, y))
|
||||||
|
.map(|z| (d.topp - z).clamp(0.0, kjellerdybde))
|
||||||
|
.unwrap_or(kjeller_over_terreng);
|
||||||
|
if i_kjeller && mot == Mot::Grunn && typ == FlateType::Vegg {
|
||||||
|
grunnmurshoyder.push(over_bakken);
|
||||||
|
}
|
||||||
let mal = if i_kjeller {
|
let mal = if i_kjeller {
|
||||||
&mut kjellerflater
|
&mut kjellerflater
|
||||||
} else {
|
} else {
|
||||||
@@ -378,8 +392,8 @@ pub fn fra_tekst_med(tekst: &str, standardnavn: &str, valg: &Importvalg) -> Resu
|
|||||||
b_faktor: None,
|
b_faktor: None,
|
||||||
absorptans: None,
|
absorptans: None,
|
||||||
omkrets: None,
|
omkrets: None,
|
||||||
dybde: (i_kjeller && mot == Mot::Grunn).then_some(kjellerdybde - kjeller_over_terreng),
|
dybde: (i_kjeller && mot == Mot::Grunn).then_some(kjellerdybde - over_bakken),
|
||||||
over_terreng: (i_kjeller && mot == Mot::Grunn).then_some(kjeller_over_terreng),
|
over_terreng: (i_kjeller && mot == Mot::Grunn).then_some(over_bakken),
|
||||||
sone,
|
sone,
|
||||||
kommentar: d
|
kommentar: d
|
||||||
.etasje
|
.etasje
|
||||||
@@ -693,7 +707,21 @@ pub fn fra_tekst_med(tekst: &str, standardnavn: &str, valg: &Importvalg) -> Resu
|
|||||||
rom.join(", ")
|
rom.join(", ")
|
||||||
)),
|
)),
|
||||||
});
|
});
|
||||||
let grunnmur = if terreng_over_gulvet {
|
let grunnmur = if !grunnmurshoyder.is_empty() && !terrengmodell.is_empty() {
|
||||||
|
let laveste = grunnmurshoyder
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.fold(f64::INFINITY, f64::min);
|
||||||
|
let hoyeste = grunnmurshoyder
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.fold(f64::NEG_INFINITY, f64::max);
|
||||||
|
let snitt = grunnmurshoyder.iter().sum::<f64>() / grunnmurshoyder.len() as f64;
|
||||||
|
format!(
|
||||||
|
"Terrengmodellen i filen gir hver kjellervegg sin egen høyde over bakken: \
|
||||||
|
{laveste:.2}–{hoyeste:.2} m, i snitt {snitt:.2} m. Resten av veggen er regnet nedgravd."
|
||||||
|
)
|
||||||
|
} else if terreng_over_gulvet {
|
||||||
format!(
|
format!(
|
||||||
"Terrengmodellen ligger over gulvet i første etasje, så den er ikke brukt. \
|
"Terrengmodellen ligger over gulvet i første etasje, så den er ikke brukt. \
|
||||||
Kjellerveggene er regnet {kjellerdybde:.1} m under terreng uten noe over. Legg \
|
Kjellerveggene er regnet {kjellerdybde:.1} m under terreng uten noe over. Legg \
|
||||||
@@ -1224,6 +1252,45 @@ fn terrenghoyder(fil: &Fil, skala: f64) -> Option<(f64, f64)> {
|
|||||||
Some((referanse, terreng))
|
Some((referanse, terreng))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Punktene i terrengmodellen, i byggets koordinater, m.
|
||||||
|
///
|
||||||
|
/// Vectorworks legger terrenget som et `IfcGeographicElement` med typen TERRAIN, eller på
|
||||||
|
/// tomta. Punktene brukes til å finne bakkenivået ved hver enkelt kjellervegg.
|
||||||
|
fn terrengpunkter(fil: &Fil, skala: f64) -> Vec<[f64; 3]> {
|
||||||
|
let mut ut = Vec::new();
|
||||||
|
for (_, e) in fil
|
||||||
|
.av_type("IFCGEOGRAPHICELEMENT")
|
||||||
|
.chain(fil.av_type("IFCSITE"))
|
||||||
|
{
|
||||||
|
let (px, py, pz) = e
|
||||||
|
.arg(5)
|
||||||
|
.and_then(|a| a.som_ref())
|
||||||
|
.and_then(|p| global_plassering(fil, p, skala))
|
||||||
|
.map(|(x, y, z, _, _)| (x, y, z))
|
||||||
|
.unwrap_or((0.0, 0.0, 0.0));
|
||||||
|
for nett in ifcgeom::netter_for_produkt(fil, e.arg(6).and_then(|a| a.som_ref())) {
|
||||||
|
for p in &nett.punkter {
|
||||||
|
ut.push([p[0] * skala + px, p[1] * skala + py, p[2] * skala + pz]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ut
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bakkenivået ved et punkt, som snittet av de nærmeste punktene i terrengmodellen, m.
|
||||||
|
fn terreng_ved(punkter: &[[f64; 3]], x: f64, y: f64) -> Option<f64> {
|
||||||
|
if punkter.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut avstand: Vec<(f64, f64)> = punkter
|
||||||
|
.iter()
|
||||||
|
.map(|p| ((p[0] - x).powi(2) + (p[1] - y).powi(2), p[2]))
|
||||||
|
.collect();
|
||||||
|
avstand.sort_by(|a, b| a.0.total_cmp(&b.0));
|
||||||
|
let antall = avstand.len().min(8);
|
||||||
|
Some(avstand[..antall].iter().map(|(_, z)| z).sum::<f64>() / antall as f64)
|
||||||
|
}
|
||||||
|
|
||||||
/// Hvor mye av grunnmuren som står over bakken, m.
|
/// Hvor mye av grunnmuren som står over bakken, m.
|
||||||
///
|
///
|
||||||
/// Toppen av grunnmuren ligger ved gulvet i første etasje over kjelleren. Den står over
|
/// Toppen av grunnmuren ligger ved gulvet i første etasje over kjelleren. Den står over
|
||||||
@@ -1251,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.
|
||||||
@@ -1439,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
|
||||||
@@ -1462,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;
|
||||||
}
|
}
|
||||||
@@ -2095,6 +2162,20 @@ mod tester {
|
|||||||
assert!(r.merknader.iter().any(|m| m.contains("standardplassering")));
|
assert!(r.merknader.iter().any(|m| m.contains("standardplassering")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn terrenget_leses_av_de_naermeste_punktene() {
|
||||||
|
// Et terreng som faller mot øst: bakken er høyere ved vestveggen enn ved østveggen.
|
||||||
|
let punkter: Vec<[f64; 3]> = (0..10)
|
||||||
|
.flat_map(|i| (0..10).map(move |j| [i as f64, j as f64, -1.0 - 0.2 * i as f64]))
|
||||||
|
.collect();
|
||||||
|
let vest = terreng_ved(&punkter, 0.5, 5.0).unwrap();
|
||||||
|
let ost = terreng_ved(&punkter, 8.5, 5.0).unwrap();
|
||||||
|
assert!(vest > ost, "vest {vest}, øst {ost}");
|
||||||
|
assert!((vest + 1.1).abs() < 0.2, "vest {vest}");
|
||||||
|
assert!((ost + 2.7).abs() < 0.2, "øst {ost}");
|
||||||
|
assert_eq!(terreng_ved(&[], 0.0, 0.0), None);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn grunnmur_over_terreng_av_hoydene() {
|
fn grunnmur_over_terreng_av_hoydene() {
|
||||||
// ±0 ligger 131,2 moh., terrenget 130,6 moh. og første etasje på ±0: 0,6 m over bakken.
|
// ±0 ligger 131,2 moh., terrenget 130,6 moh. og første etasje på ±0: 0,6 m over bakken.
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ fn kilde(r: &Resultat, scenarier: &[(String, Resultat)]) -> String {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
s.push_str(&format!(
|
s.push_str(&format!(
|
||||||
"#v(1.2cm)\n#text(size: 26pt, weight: \"bold\")[Energirapport]\n\n\
|
"#v(1.2cm)\n#forsidetittel[Energirapport]\n\n\
|
||||||
#v(-0.3em)\n#text(size: 15pt, fill: mid)[{}]\n\n",
|
#v(-0.3em)\n#text(size: 15pt, fill: mid)[{}]\n\n",
|
||||||
e(&l.navn)
|
e(&l.navn)
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -97,6 +97,16 @@
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tittelen på forsiden, i samme skrift og farge som overskriftene.
|
||||||
|
#let forsidetittel(tekst) = context {
|
||||||
|
text(
|
||||||
|
font: overskriftsskrift(),
|
||||||
|
size: 26pt,
|
||||||
|
weight: "bold",
|
||||||
|
fill: aksent-valgt.get(),
|
||||||
|
)[#tekst]
|
||||||
|
}
|
||||||
|
|
||||||
// Stor karakterrute på forsiden.
|
// Stor karakterrute på forsiden.
|
||||||
#let karakterrute(bokstav, verdi, farge) = {
|
#let karakterrute(bokstav, verdi, farge) = {
|
||||||
box(
|
box(
|
||||||
|
|||||||
Reference in New Issue
Block a user