Velg klimasted ut fra georefereringen, og rett adresse fra IFC
En ny eksport av Nordhusstykket 5 viste hva mer modellen kan bære, og avdekket feil. Posisjon og klimasted - IfcMapConversion med IfcProjectedCRS gjøres om til breddegrad og lengdegrad. EUREF89 og WGS 84 UTM, NN2000-varianter og Web Mercator forstås. Omregningen er kontrollert mot PROJ. - Vectorworks' standardplassering i Washington, som står når ingenting er satt, kjennes igjen og brukes ikke. - Ny `posisjon` under [klima]. Uten kommune velger beregningen nærmeste av de 358 klimastedene i TMY-arkivet, og sier i rapporten hvor langt unna det ligger. Opplysninger fra modellen - Adressen hentes fra IfcBuilding eller IfcSite. Før ble den første postadressen i filen brukt, og det var firmaets adresse i forfatteropplysningene. - Energirådgiver fra person og organisasjon i forfatteropplysningene. - Byggets navn fra prosjektet når bygningen heter «Default Building». - Referansehøyde og terrenghøyde på bygningen gir hvor mye av grunnmuren som står over bakken. - IsLandmarked gir en merknad om at vernede bygninger er unntatt energiattestplikten. En eksport uten bygningsdeler stopper nå med en feilmelding som peker på lagtilknytningen i eksportdialogen, i stedet for å skrive en tom bygning. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015GeH3y8fwTaTudaD88P57o
This commit is contained in:
+365
-44
@@ -11,6 +11,7 @@
|
||||
|
||||
use crate::inndata::ifcgeom;
|
||||
use crate::inndata::step::{self, Arg, Entitet, Fil};
|
||||
use crate::klima::geo;
|
||||
use crate::modell::*;
|
||||
use crate::Result;
|
||||
use anyhow::Context;
|
||||
@@ -49,6 +50,35 @@ pub fn fra_tekst(tekst: &str, standardnavn: &str) -> Result<Importrapport> {
|
||||
let (vindu_vert, dor_vert) = samle_apninger(&fil);
|
||||
let (etasjer, etasje_for) = les_etasjer(&fil, skala);
|
||||
|
||||
// En eksport uten bygningsdeler gir ingen klimaskjerm å regne på. Det skjer typisk når
|
||||
// designlagene ikke er knyttet til etasjer i eksportdialogen, og da er det bedre å stoppe
|
||||
// enn å skrive en tom bygningsbeskrivelse.
|
||||
let antall_deler: usize = [
|
||||
"IFCWALL",
|
||||
"IFCWALLSTANDARDCASE",
|
||||
"IFCCURTAINWALL",
|
||||
"IFCSLAB",
|
||||
"IFCROOF",
|
||||
"IFCWINDOW",
|
||||
"IFCDOOR",
|
||||
"IFCSPACE",
|
||||
]
|
||||
.iter()
|
||||
.map(|t| fil.av_type(t).count())
|
||||
.sum();
|
||||
if antall_deler == 0 {
|
||||
anyhow::bail!(
|
||||
"IFC-filen har ingen bygningsdeler: ingen vegger, dekker, tak, vinduer, dører eller rom. \
|
||||
Den inneholder bare prosjektstrukturen{}. I Vectorworks skjer dette gjerne når designlagene \
|
||||
ikke er med under «Layer Mapping» i «Export IFC Project».",
|
||||
if etasjer.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!(" med {} etasjer", etasjer.len())
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// ── oppvarmet areal fra rommene ─────────────────────────────────
|
||||
// Rom i en etasje under terreng hører til kjelleren og ikke til oppvarmet BRA.
|
||||
let mut bra = 0.0;
|
||||
@@ -177,6 +207,14 @@ pub fn fra_tekst(tekst: &str, standardnavn: &str) -> Result<Importrapport> {
|
||||
.unwrap_or(0.0);
|
||||
// Den laveste etasjen over terreng, der etasjeskilleren mot kjelleren ligger.
|
||||
let forste_over = etasjer.iter().position(|e| !e.under_terreng);
|
||||
// Terrenghøyden på IfcBuilding sier hvor mye av grunnmuren som står over bakken.
|
||||
let terreng = terrenghoyder(&fil, skala);
|
||||
let kjeller_over_terreng = match (terreng, forste_over.and_then(|i| etasjer.get(i))) {
|
||||
(Some((referanse, terrengnivaa)), Some(forste)) => {
|
||||
over_terreng_for_kjeller(referanse, terrengnivaa, forste.hoyde, kjellerdybde)
|
||||
}
|
||||
_ => 0.0,
|
||||
};
|
||||
|
||||
// ── klimaskjerm ─────────────────────────────────────────────────
|
||||
let mut kjellerflater: Vec<Flate> = Vec::new();
|
||||
@@ -245,8 +283,8 @@ pub fn fra_tekst(tekst: &str, standardnavn: &str) -> Result<Importrapport> {
|
||||
b_faktor: None,
|
||||
absorptans: None,
|
||||
omkrets: None,
|
||||
dybde: (i_kjeller && mot == Mot::Grunn).then_some(kjellerdybde),
|
||||
over_terreng: (i_kjeller && mot == Mot::Grunn).then_some(0.0),
|
||||
dybde: (i_kjeller && mot == Mot::Grunn).then_some(kjellerdybde - kjeller_over_terreng),
|
||||
over_terreng: (i_kjeller && mot == Mot::Grunn).then_some(kjeller_over_terreng),
|
||||
sone,
|
||||
kommentar: d
|
||||
.etasje
|
||||
@@ -407,13 +445,45 @@ pub fn fra_tekst(tekst: &str, standardnavn: &str) -> Result<Importrapport> {
|
||||
);
|
||||
}
|
||||
|
||||
let ubrukelig = |s: &str| {
|
||||
let s = s.trim();
|
||||
s.is_empty() || s == "Default Building" || s == "Vectorworks Project"
|
||||
};
|
||||
// Bygningens navn, ellers prosjektets langnavn eller navn, ellers filnavnet.
|
||||
let navn = fil
|
||||
.av_type("IFCBUILDING")
|
||||
.next()
|
||||
.and_then(|(_, e)| e.navn().map(|s| s.to_string()))
|
||||
.filter(|s| !s.is_empty() && s != "Default Building")
|
||||
.filter(|s| !ubrukelig(s))
|
||||
.or_else(|| {
|
||||
fil.av_type("IFCPROJECT").next().and_then(|(_, p)| {
|
||||
[p.arg(5), p.arg(2)]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(|a| a.som_tekst())
|
||||
.find(|s| !ubrukelig(s))
|
||||
.map(|s| s.trim().to_string())
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(|| standardnavn.to_string());
|
||||
|
||||
// Vernede bygninger er unntatt plikten til å ha energiattest.
|
||||
let vernet = fil
|
||||
.av_type("IFCBUILDING")
|
||||
.next()
|
||||
.and_then(|(r, _)| egenskaper.get(&r))
|
||||
.and_then(|e| e.get("IsLandmarked"))
|
||||
.map(|a| matches!(a.som_tekst(), Some("T") | Some("TRUE") | Some("true")))
|
||||
.unwrap_or(false);
|
||||
if vernet {
|
||||
merknader.push(
|
||||
"Bygningen er merket som vernet i modellen. Vernede bygninger etter kulturminneloven og \
|
||||
plan- og bygningsloven er unntatt plikten til å ha energiattest, men beregningen kan \
|
||||
fortsatt brukes til å vurdere tiltak."
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
// Kjellersonen settes sammen av flatene i etasjen under terreng.
|
||||
let mut soner: Vec<Sone> = Vec::new();
|
||||
if let Some(i) = kjelleretasje {
|
||||
@@ -442,11 +512,22 @@ pub fn fra_tekst(tekst: &str, standardnavn: &str) -> Result<Importrapport> {
|
||||
rom.join(", ")
|
||||
)),
|
||||
});
|
||||
let grunnmur = if terreng.is_some() {
|
||||
format!(
|
||||
"Etter terrenghøyden i modellen står grunnmuren {kjeller_over_terreng:.2} m over bakken og \
|
||||
{:.2} m under.",
|
||||
kjellerdybde - kjeller_over_terreng
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"Kjellerveggene er regnet {kjellerdybde:.1} m under terreng uten noe over. Står en del av \
|
||||
grunnmuren over bakken, sett `over_terreng` i meter på veggene, eller sett terrenghøyden \
|
||||
på bygningen i modellen. Utslaget er lite, om lag én prosent per meter."
|
||||
)
|
||||
};
|
||||
merknader.push(format!(
|
||||
"Etasjen «{}» ligger under terreng og er satt opp som en uoppvarmet sone med {:.0} m² gulv. \
|
||||
Kjellerveggene er regnet {kjellerdybde:.1} m under terreng uten noe over. Står en del av \
|
||||
grunnmuren over bakken, sett `over_terreng` i meter på veggene; utslaget er lite, om lag \
|
||||
én prosent per meter.",
|
||||
{grunnmur}",
|
||||
etasjer[i].navn, kjellerareal
|
||||
));
|
||||
merknader.push("Er kjelleren oppvarmet likevel, slett [[sone]] og legg flatene inn i klimaskjermen. \
|
||||
@@ -454,18 +535,41 @@ pub fn fra_tekst(tekst: &str, standardnavn: &str) -> Result<Importrapport> {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((lat, lon)) = posisjon(&fil) {
|
||||
merknader.push(format!(
|
||||
"Modellen oppgir posisjon {lat:.4}° nord, {lon:.4}° øst. Kontroller at den stemmer, og sett \
|
||||
`kommune` under [klima] deretter."
|
||||
));
|
||||
} else {
|
||||
merknader.push(
|
||||
"Modellen oppgir ingen posisjon. Settes prosjektstedet i Vectorworks, kommer det med som \
|
||||
IfcSite og kan brukes til å velge klimadata."
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
let klimaposisjon = match posisjon(&fil) {
|
||||
Some(p) if p.er_standardplassering() => {
|
||||
merknader.push(
|
||||
"Modellen står på Vectorworks' standardplassering i Washington, så posisjonen er ikke \
|
||||
brukt. Georefereres tegningen eller settes prosjektstedet, velger beregningen \
|
||||
klimasted ut fra den."
|
||||
.into(),
|
||||
);
|
||||
None
|
||||
}
|
||||
Some(p) if !p.i_norge() => {
|
||||
merknader.push(format!(
|
||||
"Modellen oppgir posisjon {:.4}° nord, {:.4}° øst, som ligger utenfor Norge. \
|
||||
Posisjonen er ikke brukt til å velge klimasted.",
|
||||
p.bredde, p.lengde
|
||||
));
|
||||
None
|
||||
}
|
||||
Some(p) => {
|
||||
merknader.push(format!(
|
||||
"Modellen oppgir posisjon {:.4}° nord, {:.4}° øst. Uten kommune velger beregningen \
|
||||
klimastedet nærmest denne.",
|
||||
p.bredde, p.lengde
|
||||
));
|
||||
Some([p.bredde, p.lengde])
|
||||
}
|
||||
None => {
|
||||
merknader.push(
|
||||
"Modellen oppgir ingen posisjon. Georefereres tegningen eller settes prosjektstedet, \
|
||||
velger beregningen klimasted ut fra den."
|
||||
.into(),
|
||||
);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let bygning = Bygning {
|
||||
bygning: BygningInfo {
|
||||
@@ -485,11 +589,12 @@ pub fn fra_tekst(tekst: &str, standardnavn: &str) -> Result<Importrapport> {
|
||||
adresse: adresse(&fil),
|
||||
gnr_bnr: None,
|
||||
beskrivelse: Some("Importert fra IFC".into()),
|
||||
energiraadgiver: None,
|
||||
energiraadgiver: forfatter(&fil),
|
||||
oppdragsgiver: None,
|
||||
},
|
||||
klima: Klima {
|
||||
kommune: data.kommune.clone(),
|
||||
posisjon: klimaposisjon,
|
||||
..Klima::default()
|
||||
},
|
||||
flate: flater,
|
||||
@@ -847,19 +952,66 @@ fn les_byggdata(fil: &Fil, egenskaper: &Egenskaper) -> (Byggdata, Vec<String>) {
|
||||
(d, funnet)
|
||||
}
|
||||
|
||||
/// Byggets posisjon fra `IfcSite`, som grader nord og øst.
|
||||
fn posisjon(fil: &Fil) -> Option<(f64, f64)> {
|
||||
let (_, e) = fil.av_type("IFCSITE").next()?;
|
||||
let grad = |a: Option<&Arg>| -> Option<f64> {
|
||||
let l = a?.som_liste();
|
||||
if l.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let g = |i: usize| l.get(i).and_then(|x| x.som_tall()).unwrap_or(0.0);
|
||||
let verdi = g(0).abs() + g(1).abs() / 60.0 + g(2).abs() / 3600.0 + g(3).abs() / 3.6e9;
|
||||
Some(if g(0) < 0.0 { -verdi } else { verdi })
|
||||
};
|
||||
Some((grad(e.arg(9))?, grad(e.arg(10))?))
|
||||
/// Byggets posisjon.
|
||||
///
|
||||
/// Georeferering gjennom `IfcMapConversion` og `IfcProjectedCRS` brukes først, siden den er
|
||||
/// mest presis. Ellers brukes breddegrad og lengdegrad på `IfcSite`. Vectorworks fyller begge
|
||||
/// med en standardplassering i Washington når ingenting er satt, og den velges bare når ingen
|
||||
/// annen posisjon finnes, slik at den kan kjennes igjen og oversees.
|
||||
fn posisjon(fil: &Fil) -> Option<geo::Posisjon> {
|
||||
let fra_kart = fil.av_type("IFCMAPCONVERSION").next().and_then(|(_, m)| {
|
||||
let crs = m
|
||||
.arg(1)
|
||||
.and_then(|a| a.som_ref())
|
||||
.and_then(|r| fil.hent(r))?;
|
||||
let navn = crs.arg(0).and_then(|a| a.som_tekst())?;
|
||||
let x = m.arg(2).and_then(|a| a.som_tall())?;
|
||||
let y = m.arg(3).and_then(|a| a.som_tall())?;
|
||||
geo::fra_koordinatsystem(navn, x, y)
|
||||
});
|
||||
let fra_tomt = fil.av_type("IFCSITE").next().and_then(|(_, e)| {
|
||||
let grad = |a: Option<&Arg>| -> Option<f64> {
|
||||
let l = a?.som_liste();
|
||||
if l.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let g = |i: usize| l.get(i).and_then(|x| x.som_tall()).unwrap_or(0.0);
|
||||
let verdi = g(0).abs() + g(1).abs() / 60.0 + g(2).abs() / 3600.0 + g(3).abs() / 3.6e9;
|
||||
Some(if g(0) < 0.0 { -verdi } else { verdi })
|
||||
};
|
||||
Some(geo::Posisjon {
|
||||
bredde: grad(e.arg(9))?,
|
||||
lengde: grad(e.arg(10))?,
|
||||
})
|
||||
});
|
||||
[fra_kart, fra_tomt]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.find(|p| !p.er_standardplassering())
|
||||
.or(fra_kart)
|
||||
.or(fra_tomt)
|
||||
}
|
||||
|
||||
/// Referansehøyden og terrenghøyden på bygningen, begge over havet, m.
|
||||
fn terrenghoyder(fil: &Fil, skala: f64) -> Option<(f64, f64)> {
|
||||
let (_, e) = fil.av_type("IFCBUILDING").next()?;
|
||||
let referanse = e.arg(9).and_then(|a| a.som_tall())? * skala;
|
||||
let terreng = e.arg(10).and_then(|a| a.som_tall())? * skala;
|
||||
Some((referanse, terreng))
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// terrenget med differansen mellom etasjens høyde over havet og terrengets, begrenset til
|
||||
/// kjellerens høyde.
|
||||
fn over_terreng_for_kjeller(
|
||||
referanse: f64,
|
||||
terreng: f64,
|
||||
forste_etasje: f64,
|
||||
kjellerdybde: f64,
|
||||
) -> f64 {
|
||||
((referanse + forste_etasje) - terreng).clamp(0.0, kjellerdybde.max(0.0))
|
||||
}
|
||||
|
||||
/// En bygningsdel slik den er lest av IFC-filen.
|
||||
@@ -1097,23 +1249,93 @@ fn navn_av(e: &Entitet, r: u64, standard: &str) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
/// Byggets adresse fra `IfcBuilding.BuildingAddress`, eller fra tomta.
|
||||
///
|
||||
/// Adresser knyttet til personer og organisasjoner er forfatterens, ikke byggets, og brukes
|
||||
/// ikke. Plassholdere uten gate eller poststed, som Vectorworks setter inn, hoppes over.
|
||||
fn adresse(fil: &Fil) -> Option<String> {
|
||||
let (_, e) = fil.av_type("IFCPOSTALADDRESS").next()?;
|
||||
let bygning = fil
|
||||
.av_type("IFCBUILDING")
|
||||
.filter_map(|(_, e)| e.arg(11).and_then(|a| a.som_ref()));
|
||||
let tomt = fil
|
||||
.av_type("IFCSITE")
|
||||
.filter_map(|(_, e)| e.arg(13).and_then(|a| a.som_ref()));
|
||||
bygning
|
||||
.chain(tomt)
|
||||
.filter_map(|r| fil.hent(r))
|
||||
.filter(|e| e.typ == "IFCPOSTALADDRESS")
|
||||
.find_map(formater_adresse)
|
||||
}
|
||||
|
||||
/// `IfcPostalAddress` har feltene Purpose, Description, UserDefinedPurpose, InternalLocation,
|
||||
/// AddressLines, PostalBox, Town, Region, PostalCode og Country.
|
||||
fn formater_adresse(e: &Entitet) -> Option<String> {
|
||||
let linjer: Vec<String> = e
|
||||
.arg(4)?
|
||||
.som_liste()
|
||||
.arg(4)
|
||||
.map(|a| a.som_liste())
|
||||
.unwrap_or(&[])
|
||||
.iter()
|
||||
.filter_map(|a| a.som_tekst().map(|s| s.to_string()))
|
||||
.filter_map(|a| a.som_tekst())
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect();
|
||||
let by = e.arg(6).and_then(|a| a.som_tekst()).unwrap_or("");
|
||||
let mut ut = linjer.join(", ");
|
||||
if !by.is_empty() {
|
||||
if !ut.is_empty() {
|
||||
ut.push_str(", ");
|
||||
}
|
||||
ut.push_str(by);
|
||||
let postnummer = e.arg(8).and_then(|a| a.som_tekst()).unwrap_or("").trim();
|
||||
let sted = e.arg(6).and_then(|a| a.som_tekst()).unwrap_or("").trim();
|
||||
if linjer.is_empty() && sted.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let poststed: Vec<&str> = [postnummer, sted]
|
||||
.into_iter()
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect();
|
||||
Some(
|
||||
linjer
|
||||
.into_iter()
|
||||
.chain((!poststed.is_empty()).then(|| poststed.join(" ")))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", "),
|
||||
)
|
||||
}
|
||||
|
||||
/// Forfatteren av modellen, som person og organisasjon.
|
||||
///
|
||||
/// I en modell laget for energirådgivning er det energirådgiveren. Vectorworks' egen
|
||||
/// organisasjon, som står når ingenting er fylt inn, tas ikke med.
|
||||
fn forfatter(fil: &Fil) -> Option<String> {
|
||||
let (_, po) = fil.av_type("IFCPERSONANDORGANIZATION").next()?;
|
||||
let person = po
|
||||
.arg(0)
|
||||
.and_then(|a| a.som_ref())
|
||||
.and_then(|r| fil.hent(r));
|
||||
let organisasjon = po
|
||||
.arg(1)
|
||||
.and_then(|a| a.som_ref())
|
||||
.and_then(|r| fil.hent(r));
|
||||
// IfcPerson: Identification, FamilyName, GivenName, …
|
||||
let personnavn = person
|
||||
.map(|p| {
|
||||
[p.arg(2), p.arg(1)]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(|a| a.som_tekst())
|
||||
.map(str::trim)
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
})
|
||||
.filter(|s| !s.is_empty());
|
||||
// IfcOrganization: Identification, Name, …
|
||||
let organisasjonsnavn = organisasjon
|
||||
.and_then(|o| o.arg(1).and_then(|a| a.som_tekst()))
|
||||
.map(str::trim)
|
||||
.filter(|s| !s.is_empty() && !s.to_lowercase().contains("vectorworks"))
|
||||
.map(str::to_string);
|
||||
match (personnavn, organisasjonsnavn) {
|
||||
(Some(p), Some(o)) => Some(format!("{p}, {o}")),
|
||||
(Some(p), None) => Some(p),
|
||||
(None, Some(o)) => Some(o),
|
||||
(None, None) => None,
|
||||
}
|
||||
(!ut.is_empty()).then_some(ut)
|
||||
}
|
||||
|
||||
/// Prosjektets dreining mot sant nord, i grader med klokka.
|
||||
@@ -1500,6 +1722,105 @@ mod tester {
|
||||
assert!(r.merknader.iter().any(|m| m.contains("tatt i bruk")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eksport_uten_bygningsdeler_gir_tydelig_feil() {
|
||||
let tom = "ISO-10303-21;\nHEADER;ENDSEC;\nDATA;\n\
|
||||
#1= IFCSIUNIT(*,.LENGTHUNIT.,.MILLI.,.METRE.);\n\
|
||||
#2= IFCUNITASSIGNMENT((#1));\n\
|
||||
#3= IFCPROJECT('p',$,'Prosjekt',$,$,$,$,$,#2);\n\
|
||||
#10= IFCBUILDING('b',$,'Hus',$,$,$,$,$,.ELEMENT.,$,$,$);\n\
|
||||
#11= IFCBUILDINGSTOREY('k',$,'Kjeller',$,$,$,$,$,.ELEMENT.,-2350.);\n\
|
||||
ENDSEC;\nEND-ISO-10303-21;";
|
||||
let feil = fra_tekst(tom, "tom")
|
||||
.err()
|
||||
.expect("en tom eksport skal gi feil");
|
||||
let tekst = feil.to_string();
|
||||
assert!(tekst.contains("ingen bygningsdeler"), "{tekst}");
|
||||
assert!(tekst.contains("1 etasjer"), "{tekst}");
|
||||
}
|
||||
|
||||
/// Adressen på firmaet som laget modellen skal ikke bli byggets adresse.
|
||||
#[test]
|
||||
fn adressen_hentes_fra_bygningen_og_ikke_fra_forfatteren() {
|
||||
let t = prove().replace(
|
||||
"#10= IFCBUILDING('b',$,'Testhus',$,$,#20,$,$,.ELEMENT.,$,$,$);",
|
||||
"#501= IFCPOSTALADDRESS($,$,$,$,('Hob\\X\\F8lveien 6'),$,'Tomter',$,'1825','Norge');\n\
|
||||
#502= IFCORGANIZATION('976015711','Klingenberg Bygg',$,$,(#501));\n\
|
||||
#503= IFCPERSON($,'Lynghaug','Fredrik',$,$,$,$,$);\n\
|
||||
#504= IFCPERSONANDORGANIZATION(#503,#502,$);\n\
|
||||
#500= IFCPOSTALADDRESS($,$,$,$,('Nordhusstykket 5'),$,'Tomter',$,'1825','Norge');\n\
|
||||
#10= IFCBUILDING('b',$,'Testhus',$,$,#20,$,$,.ELEMENT.,$,$,#500);",
|
||||
);
|
||||
let r = fra_tekst(&t, "prove").unwrap();
|
||||
assert_eq!(
|
||||
r.bygning.bygning.adresse.as_deref(),
|
||||
Some("Nordhusstykket 5, 1825 Tomter")
|
||||
);
|
||||
assert_eq!(
|
||||
r.bygning.bygning.energiraadgiver.as_deref(),
|
||||
Some("Fredrik Lynghaug, Klingenberg Bygg")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plassholderadresse_og_standardnavn_brukes_ikke() {
|
||||
let t = prove()
|
||||
.replace(
|
||||
"#10= IFCBUILDING('b',$,'Testhus',$,$,#20,$,$,.ELEMENT.,$,$,$);",
|
||||
"#500= IFCPOSTALADDRESS($,$,$,'Vectorworks Architect',$,$,$,$,$,$);\n\
|
||||
#10= IFCBUILDING('b',$,'Default Building',$,$,#20,$,$,.ELEMENT.,$,$,#500);",
|
||||
)
|
||||
.replace(
|
||||
"#3= IFCPROJECT('p',$,'Prosjekt',$,$,$,$,(#40),#2);",
|
||||
"#3= IFCPROJECT('p',$,'Nordhusstykket 5','Energianalyse',$,'Nordhusstykket 5','Eksisterende',(#40),#2);",
|
||||
);
|
||||
let r = fra_tekst(&t, "prove").unwrap();
|
||||
assert_eq!(r.bygning.bygning.adresse, None);
|
||||
assert_eq!(r.bygning.bygning.navn, "Nordhusstykket 5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn georeferering_i_utm_gir_posisjon_for_klimavalg() {
|
||||
let t = prove().replace(
|
||||
"#4= IFCDIRECTION((0.,1.));",
|
||||
"#4= IFCDIRECTION((0.,1.));\n\
|
||||
#600= IFCPROJECTEDCRS('EPSG:25832','ETRS89 / UTM zone 32N','ETRS89',$,$,$,$);\n\
|
||||
#601= IFCMAPCONVERSION(#40,#600,613351.291,6611921.980,0.,1.,0.,$);",
|
||||
);
|
||||
let r = fra_tekst(&t, "prove").unwrap();
|
||||
let [b, l] = r
|
||||
.bygning
|
||||
.klima
|
||||
.posisjon
|
||||
.expect("posisjon fra georefereringen");
|
||||
assert!(
|
||||
(b - 59.63).abs() < 1e-4 && (l - 11.01).abs() < 1e-4,
|
||||
"{b}, {l}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vectorworks_standardplassering_brukes_ikke() {
|
||||
let t = prove().replace(
|
||||
"#4= IFCDIRECTION((0.,1.));",
|
||||
"#4= IFCDIRECTION((0.,1.));\n\
|
||||
#600= IFCPROJECTEDCRS('EPSG:3857',$,'WGS 84',$,$,$,$);\n\
|
||||
#601= IFCMAPCONVERSION(#40,#600,-8575524.35228859,4705852.25746973,0.,1.,0.,$);",
|
||||
);
|
||||
let r = fra_tekst(&t, "prove").unwrap();
|
||||
assert_eq!(r.bygning.klima.posisjon, None);
|
||||
assert!(r.merknader.iter().any(|m| m.contains("standardplassering")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
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.
|
||||
assert!((over_terreng_for_kjeller(131.2, 130.6, 0.0, 2.35) - 0.6).abs() < 1e-9);
|
||||
// Aldri mer enn kjellerhøyden, og aldri under null.
|
||||
assert_eq!(over_terreng_for_kjeller(131.2, 125.0, 0.0, 2.35), 2.35);
|
||||
assert_eq!(over_terreng_for_kjeller(130.0, 131.0, 0.0, 2.35), 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vegg_uten_geometri_utelates() {
|
||||
let uten = prove().replace("'Body','Tessellation'", "'Axis','Curve2D'");
|
||||
|
||||
Reference in New Issue
Block a user