Beregn rett fra en BIM-modell, uten bygning.toml
«enok beregn modell.ifc» leser modellen, fletter inn en eventuell tilleggsfil og skriver bygningsbeskrivelsen den kom fram til, i utdatakatalogen, slik at beregningen kan etterprøves. Importen sier nå hvilke bygningsdeler som mangler U-verdi, ikke bare hvor mange. Vinduer og dører uten U-verdi i modellen får en typisk verdi for byggeåret, slik flatene alltid har fått, i stedet for faste 2,4 og 2,0 W/(m²K). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015GeH3y8fwTaTudaD88P57o
This commit is contained in:
@@ -95,13 +95,14 @@ Typst må være installert for PDF-rapporten. Uten Typst kjører du med `--ingen
|
|||||||
| Kommando | Hva den gjør |
|
| Kommando | Hva den gjør |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `enok beregn FIL.toml` | Beregner og skriver rapport, CSV og JSON til `ut/<navn>/` |
|
| `enok beregn FIL.toml` | Beregner og skriver rapport, CSV og JSON til `ut/<navn>/` |
|
||||||
|
| `enok beregn modell.ifc` | Beregner rett fra en modell, uten at `bygning.toml` finnes fra før |
|
||||||
| `enok import FIL` | Leser IFC, gbXML eller regneark og skriver `bygning.toml` |
|
| `enok import FIL` | Leser IFC, gbXML eller regneark og skriver `bygning.toml` |
|
||||||
| `enok import FIL --tillegg prosjekt.toml` | Som over, med en tilleggsfil flettet inn (se nedenfor) |
|
| `enok import FIL --tillegg prosjekt.toml` | Som over, med en tilleggsfil flettet inn (se nedenfor) |
|
||||||
| `enok klima --liste` | Lister alle kommuner med klimadata |
|
| `enok klima --liste` | Lister alle kommuner med klimadata |
|
||||||
| `enok klima --kommune Oslo` | Viser nøkkeltall for ett klimasted |
|
| `enok klima --kommune Oslo` | Viser nøkkeltall for ett klimasted |
|
||||||
| `enok skala smaahus --areal 140` | Viser karakterskalaen med arealkorreksjon |
|
| `enok skala smaahus --areal 140` | Viser karakterskalaen med arealkorreksjon |
|
||||||
|
|
||||||
Nyttige valg til `beregn`: `--ingen-pdf`, `--ingen-timeserie`, `--enova-xml`,
|
Nyttige valg til `beregn`: `--tillegg FIL`, `--ingen-pdf`, `--ingen-timeserie`, `--enova-xml`,
|
||||||
`--uten-scenarier`, `--ut KATALOG`.
|
`--uten-scenarier`, `--ut KATALOG`.
|
||||||
|
|
||||||
### Utdata
|
### Utdata
|
||||||
@@ -194,15 +195,21 @@ Ferdige eksempler ligger i `eksempler/`.
|
|||||||
|
|
||||||
### Modellen og en tilleggsfil
|
### Modellen og en tilleggsfil
|
||||||
|
|
||||||
Modellen bør være kilden til alt den kan beskrive. Det den ikke beskriver, som varmeanlegg,
|
Modellen er kilden til alt den kan beskrive, og en modell alene gir en ferdig rapport: enok
|
||||||
lekkasjetall og tiltak, og rettelser der den mangler noe, legges i en tilleggsfil som flettes
|
fyller hullene med normerte verdier og typiske verdier for byggeåret, og rapporten sier hva
|
||||||
inn ved hver import etter de samme reglene som scenarioene:
|
som er antatt. Det modellen ikke kan beskrive, som varmeanlegg, lekkasjetall og tiltak, og
|
||||||
|
rettelser der den mangler noe, legges i en tilleggsfil som flettes inn etter de samme reglene
|
||||||
|
som scenarioene:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
enok import modell.ifc --tillegg prosjekt.toml --ut bygning.toml
|
enok beregn modell.ifc # bare modellen
|
||||||
enok beregn bygning.toml
|
enok beregn modell.ifc --tillegg prosjekt.toml # modellen og tilleggsfilen
|
||||||
|
enok import modell.ifc --tillegg prosjekt.toml --ut bygning.toml # samme, som fil
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`beregn` skriver bygningsbeskrivelsen modellen ga, til `bygning.toml` i utdatakatalogen, slik
|
||||||
|
at beregningen kan etterprøves.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
# prosjekt.toml
|
# prosjekt.toml
|
||||||
[import]
|
[import]
|
||||||
@@ -284,6 +291,10 @@ egenskapssett på `IfcBuilding`, `IfcSite` eller `IfcProject`:
|
|||||||
| `Varmekilde` | `Oppvarming`, `HeatingSource` | `Bergvarmepumpe` |
|
| `Varmekilde` | `Oppvarming`, `HeatingSource` | `Bergvarmepumpe` |
|
||||||
| `OppvarmetBRA` | `HeatedFloorArea` | `120` |
|
| `OppvarmetBRA` | `HeatedFloorArea` | `120` |
|
||||||
|
|
||||||
|
Mangler en bygningsdel U-verdi i modellen, sier importen hvilke det gjelder, og beregningen
|
||||||
|
bruker en typisk verdi for byggeåret. Det gjelder også vinduer og dører, som i de fleste
|
||||||
|
eksportene ikke har U-verdi.
|
||||||
|
|
||||||
Med de åtte feltene på plass er importen fra IFC nær nok komplett til at beregningen står på
|
Med de åtte feltene på plass er importen fra IFC nær nok komplett til at beregningen står på
|
||||||
modellen alene.
|
modellen alene.
|
||||||
|
|
||||||
|
|||||||
@@ -481,13 +481,13 @@ fn bygg(
|
|||||||
let areal = areal_av(a.areal, a.bredde, a.hoyde, &a.polygon);
|
let areal = areal_av(a.areal, a.bredde, a.hoyde, &a.polygon);
|
||||||
aapningsareal += areal;
|
aapningsareal += areal;
|
||||||
let vt = vindustyper.get(&a.vindustype);
|
let vt = vindustyper.get(&a.vindustype);
|
||||||
let u = vt.and_then(|v| v.u_verdi).unwrap_or_else(|| {
|
let u = vt.and_then(|v| v.u_verdi);
|
||||||
|
if u.is_none() {
|
||||||
merknader.push(format!(
|
merknader.push(format!(
|
||||||
"Åpningen «{}» mangler U-verdi i gbXML; satt til 2,4 W/(m²K).",
|
"Åpningen «{}» mangler U-verdi i gbXML; verdien utledes av byggeåret.",
|
||||||
a.navn
|
a.navn
|
||||||
));
|
));
|
||||||
2.4
|
}
|
||||||
});
|
|
||||||
let dor = a.typ.to_lowercase().contains("door");
|
let dor = a.typ.to_lowercase().contains("door");
|
||||||
vinduer.push(Vindu {
|
vinduer.push(Vindu {
|
||||||
navn: vt
|
navn: vt
|
||||||
@@ -687,7 +687,7 @@ mod tester {
|
|||||||
// Vinduet.
|
// Vinduet.
|
||||||
assert_eq!(b.vindu.len(), 1);
|
assert_eq!(b.vindu.len(), 1);
|
||||||
assert_eq!(b.vindu[0].areal, 3.0);
|
assert_eq!(b.vindu[0].areal, 3.0);
|
||||||
assert_eq!(b.vindu[0].u_verdi, 1.2);
|
assert_eq!(b.vindu[0].u_verdi, Some(1.2));
|
||||||
assert_eq!(b.vindu[0].g_verdi, Some(0.5));
|
assert_eq!(b.vindu[0].g_verdi, Some(0.5));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+32
-12
@@ -245,7 +245,7 @@ pub fn fra_tekst_med(tekst: &str, standardnavn: &str, valg: &Importvalg) -> Resu
|
|||||||
let mut kjellerflater: Vec<Flate> = Vec::new();
|
let mut kjellerflater: Vec<Flate> = Vec::new();
|
||||||
let mut flater: Vec<Flate> = Vec::new();
|
let mut flater: Vec<Flate> = Vec::new();
|
||||||
let mut utelatt: Vec<(String, f64, Option<f64>)> = Vec::new();
|
let mut utelatt: Vec<(String, f64, Option<f64>)> = Vec::new();
|
||||||
let mut uten_uverdi = 0usize;
|
let mut uten_uverdi: Vec<String> = Vec::new();
|
||||||
let mut uten_geometri = 0usize;
|
let mut uten_geometri = 0usize;
|
||||||
let mut asimut_for_vegg: HashMap<u64, f64> = HashMap::new();
|
let mut asimut_for_vegg: HashMap<u64, f64> = HashMap::new();
|
||||||
|
|
||||||
@@ -287,7 +287,7 @@ pub fn fra_tekst_med(tekst: &str, standardnavn: &str, valg: &Importvalg) -> Resu
|
|||||||
(mot, None)
|
(mot, None)
|
||||||
};
|
};
|
||||||
if d.u_verdi.is_none() {
|
if d.u_verdi.is_none() {
|
||||||
uten_uverdi += 1;
|
uten_uverdi.push(d.navn.clone());
|
||||||
}
|
}
|
||||||
let mal = if i_kjeller {
|
let mal = if i_kjeller {
|
||||||
&mut kjellerflater
|
&mut kjellerflater
|
||||||
@@ -358,14 +358,12 @@ pub fn fra_tekst_med(tekst: &str, standardnavn: &str, valg: &Importvalg) -> Resu
|
|||||||
.and_then(|v| asimut_for_vegg.get(v))
|
.and_then(|v| asimut_for_vegg.get(v))
|
||||||
.copied()
|
.copied()
|
||||||
.unwrap_or(0.0);
|
.unwrap_or(0.0);
|
||||||
let u = u_verdi(&egenskaper, r).unwrap_or_else(|| {
|
// Uten U-verdi i modellen lar vi feltet stå tomt, slik at beregningen bruker en
|
||||||
uten_uverdi += 1;
|
// typisk verdi for byggeåret i stedet for en fast standardverdi.
|
||||||
if dor {
|
let u = u_verdi(&egenskaper, r);
|
||||||
2.0
|
if u.is_none() {
|
||||||
} else {
|
uten_uverdi.push(navn_av(e, r, if dor { "Dør" } else { "Vindu" }));
|
||||||
2.4
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
vinduer.push(Vindu {
|
vinduer.push(Vindu {
|
||||||
navn: navn_av(e, r, if dor { "Dør" } else { "Vindu" }),
|
navn: navn_av(e, r, if dor { "Dør" } else { "Vindu" }),
|
||||||
areal,
|
areal,
|
||||||
@@ -445,9 +443,31 @@ pub fn fra_tekst_med(tekst: &str, standardnavn: &str, valg: &Importvalg) -> Resu
|
|||||||
"{uten_geometri} bygningsdeler er utelatt fordi geometrien ikke ga noe areal."
|
"{uten_geometri} bygningsdeler er utelatt fordi geometrien ikke ga noe areal."
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if uten_uverdi > 0 {
|
if !uten_uverdi.is_empty() {
|
||||||
|
// Navnene gjør det tydelig hva som må rettes i modellen. Flere elementer av samme
|
||||||
|
// konstruksjon nevnes én gang, med antallet.
|
||||||
|
let mut antall: Vec<(String, usize)> = Vec::new();
|
||||||
|
for navn in &uten_uverdi {
|
||||||
|
let kort = navn.split(" #").next().unwrap_or(navn).trim().to_string();
|
||||||
|
match antall.iter_mut().find(|(n, _)| *n == kort) {
|
||||||
|
Some((_, a)) => *a += 1,
|
||||||
|
None => antall.push((kort, 1)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let liste: Vec<String> = antall
|
||||||
|
.iter()
|
||||||
|
.map(|(n, a)| {
|
||||||
|
if *a > 1 {
|
||||||
|
format!("{n} ×{a}")
|
||||||
|
} else {
|
||||||
|
n.clone()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
merknader.push(format!(
|
merknader.push(format!(
|
||||||
"{uten_uverdi} bygningsdeler mangler U-verdi i IFC-filen. Legg inn U-verdier, eller la enok bruke typiske verdier for byggeåret."
|
"{} bygningsdeler mangler U-verdi i IFC-filen: {}. Legg inn U-verdier i modellen, eller la enok bruke typiske verdier for byggeåret.",
|
||||||
|
uten_uverdi.len(),
|
||||||
|
liste.join(", ")
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
merknader.push(
|
merknader.push(
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ pub fn fra_tekst(tekst: &str, standardnavn: &str) -> Result<Importrapport> {
|
|||||||
antall: tall(felt(8)).unwrap_or(1.0),
|
antall: tall(felt(8)).unwrap_or(1.0),
|
||||||
orientering: tall(felt(4)).unwrap_or(0.0),
|
orientering: tall(felt(4)).unwrap_or(0.0),
|
||||||
helning: tall(felt(5)).unwrap_or(90.0),
|
helning: tall(felt(5)).unwrap_or(90.0),
|
||||||
u_verdi: u,
|
u_verdi: Some(u),
|
||||||
g_verdi: tall(felt(7)),
|
g_verdi: tall(felt(7)),
|
||||||
karmandel: None,
|
karmandel: None,
|
||||||
solskjerming: None,
|
solskjerming: None,
|
||||||
|
|||||||
+99
-60
@@ -56,6 +56,9 @@ struct Beregn {
|
|||||||
/// Beregner bare hovedalternativet, uten scenarioene i filen.
|
/// Beregner bare hovedalternativet, uten scenarioene i filen.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
uten_scenarier: bool,
|
uten_scenarier: bool,
|
||||||
|
/// Tilleggsfil som flettes inn: det modellen ikke beskriver, og rettelser.
|
||||||
|
#[arg(short, long, value_name = "FIL")]
|
||||||
|
tillegg: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
@@ -102,14 +105,77 @@ fn main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Leser en bygningsbeskrivelse eller en BIM-modell, med en valgfri tilleggsfil flettet inn.
|
||||||
|
///
|
||||||
|
/// En `.toml`-fil er bygningsbeskrivelsen selv; andre filtyper importeres. Returnerer også
|
||||||
|
/// merknadene fra importen, og om filen var en modell.
|
||||||
|
fn les_modell(
|
||||||
|
fil: &Path,
|
||||||
|
tillegg: Option<&Path>,
|
||||||
|
) -> Result<(enok::modell::Bygning, Vec<String>, bool)> {
|
||||||
|
let endelse = fil
|
||||||
|
.extension()
|
||||||
|
.and_then(|e| e.to_str())
|
||||||
|
.unwrap_or("")
|
||||||
|
.to_lowercase();
|
||||||
|
let tillegg = tillegg.map(inndata::les_tillegg).transpose()?;
|
||||||
|
let valg = tillegg
|
||||||
|
.as_ref()
|
||||||
|
.map(|t| t.import.clone())
|
||||||
|
.unwrap_or_default();
|
||||||
|
let (bygning, mut merknader, modell) = match endelse.as_str() {
|
||||||
|
"toml" => (inndata::les_bygning(fil)?, Vec::new(), false),
|
||||||
|
"gbxml" | "xml" => {
|
||||||
|
let r = inndata::gbxml::les(fil)?;
|
||||||
|
(r.bygning, r.merknader, true)
|
||||||
|
}
|
||||||
|
"ifc" => {
|
||||||
|
let r = inndata::ifc::les_med(fil, &valg)?;
|
||||||
|
(r.bygning, r.merknader, true)
|
||||||
|
}
|
||||||
|
"csv" => {
|
||||||
|
let r = inndata::regneark::les(fil)?;
|
||||||
|
(r.bygning, r.merknader, true)
|
||||||
|
}
|
||||||
|
annen => bail!(
|
||||||
|
"ukjent filtype «{annen}». enok leser bygning.toml og importerer .gbxml, .ifc og .csv (regneark fra Vectorworks)"
|
||||||
|
),
|
||||||
|
};
|
||||||
|
if endelse != "ifc" && !valg.uoppvarmede_rom.is_empty() {
|
||||||
|
merknader.push("uoppvarmede_rom i tilleggsfilen brukes bare ved import fra IFC.".into());
|
||||||
|
}
|
||||||
|
let Some(t) = tillegg else {
|
||||||
|
return Ok((bygning, merknader, modell));
|
||||||
|
};
|
||||||
|
let (bygning, bom) = inndata::flett_tillegg(&bygning, &t.endringer)?;
|
||||||
|
for m in bom {
|
||||||
|
merknader.push(format!(
|
||||||
|
"Navnemønsteret «{m}» i tilleggsfilen traff ingen oppføring i modellen."
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok((bygning, merknader, modell))
|
||||||
|
}
|
||||||
|
|
||||||
fn beregn(a: Beregn, kilde: &Datakilde) -> Result<()> {
|
fn beregn(a: Beregn, kilde: &Datakilde) -> Result<()> {
|
||||||
let bygning = inndata::les_bygning(&a.fil)?;
|
let (bygning, merknader, modell) = les_modell(&a.fil, a.tillegg.as_deref())?;
|
||||||
|
for m in &merknader {
|
||||||
|
eprintln!(" · {m}");
|
||||||
|
}
|
||||||
for m in enok::modell::kontroll::kontroller(&bygning) {
|
for m in enok::modell::kontroll::kontroller(&bygning) {
|
||||||
eprintln!(" ! {}: {}", m.felt, m.melding);
|
eprintln!(" ! {}: {}", m.felt, m.melding);
|
||||||
}
|
}
|
||||||
let ut =
|
let ut =
|
||||||
a.ut.unwrap_or_else(|| PathBuf::from("ut").join(filnavnvennlig(&bygning.bygning.navn)));
|
a.ut.unwrap_or_else(|| PathBuf::from("ut").join(filnavnvennlig(&bygning.bygning.navn)));
|
||||||
std::fs::create_dir_all(&ut)?;
|
std::fs::create_dir_all(&ut)?;
|
||||||
|
if modell {
|
||||||
|
// Bygningsbeskrivelsen modellen ga, skrives med, slik at beregningen kan etterprøves.
|
||||||
|
let sti = ut.join("bygning.toml");
|
||||||
|
std::fs::write(
|
||||||
|
&sti,
|
||||||
|
skriv_bygning(&bygning, &merknader, &a.fil, a.tillegg.as_deref())?,
|
||||||
|
)?;
|
||||||
|
eprintln!("Bygningsbeskrivelsen fra modellen: {}", sti.display());
|
||||||
|
}
|
||||||
|
|
||||||
eprintln!("Henter klimadata …");
|
eprintln!("Henter klimadata …");
|
||||||
let (vaerdata, klimanavn, er_referanse) = kjor::hent_klima(&bygning)?;
|
let (vaerdata, klimanavn, er_referanse) = kjor::hent_klima(&bygning)?;
|
||||||
@@ -215,66 +281,9 @@ fn skriv_sammendrag(r: &kjor::Resultat, scenarier: &[(String, kjor::Resultat)])
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn import(a: Import) -> Result<()> {
|
fn import(a: Import) -> Result<()> {
|
||||||
let endelse = a
|
let (bygning, merknader, _) = les_modell(&a.fil, a.tillegg.as_deref())?;
|
||||||
.fil
|
|
||||||
.extension()
|
|
||||||
.and_then(|e| e.to_str())
|
|
||||||
.unwrap_or("")
|
|
||||||
.to_lowercase();
|
|
||||||
let tillegg = a.tillegg.as_deref().map(inndata::les_tillegg).transpose()?;
|
|
||||||
let valg = tillegg
|
|
||||||
.as_ref()
|
|
||||||
.map(|t| t.import.clone())
|
|
||||||
.unwrap_or_default();
|
|
||||||
let (bygning, mut merknader) = match endelse.as_str() {
|
|
||||||
"gbxml" | "xml" => {
|
|
||||||
let r = inndata::gbxml::les(&a.fil)?;
|
|
||||||
(r.bygning, r.merknader)
|
|
||||||
}
|
|
||||||
"ifc" => {
|
|
||||||
let r = inndata::ifc::les_med(&a.fil, &valg)?;
|
|
||||||
(r.bygning, r.merknader)
|
|
||||||
}
|
|
||||||
"csv" => {
|
|
||||||
let r = inndata::regneark::les(&a.fil)?;
|
|
||||||
(r.bygning, r.merknader)
|
|
||||||
}
|
|
||||||
annen => bail!(
|
|
||||||
"ukjent filtype «{annen}». enok importerer .gbxml, .ifc og .csv (regneark fra Vectorworks)"
|
|
||||||
),
|
|
||||||
};
|
|
||||||
if endelse != "ifc" && !valg.uoppvarmede_rom.is_empty() {
|
|
||||||
merknader.push("uoppvarmede_rom i tilleggsfilen brukes bare ved import fra IFC.".into());
|
|
||||||
}
|
|
||||||
let (bygning, mut innhold) = match (&tillegg, &a.tillegg) {
|
|
||||||
(Some(t), Some(sti)) => {
|
|
||||||
let (b, bom) = inndata::flett_tillegg(&bygning, &t.endringer)?;
|
|
||||||
for m in bom {
|
|
||||||
merknader.push(format!(
|
|
||||||
"Navnemønsteret «{m}» i tilleggsfilen traff ingen oppføring i modellen."
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let hode = format!(
|
|
||||||
"# Generert av enok fra «{}» og tilleggsfilen «{}».\n\
|
|
||||||
# Rett i modellen eller i tilleggsfilen; denne filen skrives på nytt ved hver import.\n\
|
|
||||||
# Merknader fra importen:\n",
|
|
||||||
a.fil.display(),
|
|
||||||
sti.display()
|
|
||||||
);
|
|
||||||
(b, hode)
|
|
||||||
}
|
|
||||||
_ => (
|
|
||||||
bygning,
|
|
||||||
String::from("# Importert med enok. Fyll ut feltene som er merket nedenfor.\n"),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
let ut = a.ut.unwrap_or_else(|| a.fil.with_extension("toml"));
|
let ut = a.ut.unwrap_or_else(|| a.fil.with_extension("toml"));
|
||||||
let tekst = toml::to_string_pretty(&bygning).context("serialiserer bygningen til TOML")?;
|
let innhold = skriv_bygning(&bygning, &merknader, &a.fil, a.tillegg.as_deref())?;
|
||||||
for m in &merknader {
|
|
||||||
innhold.push_str(&format!("# {m}\n"));
|
|
||||||
}
|
|
||||||
innhold.push('\n');
|
|
||||||
innhold.push_str(&tekst);
|
|
||||||
std::fs::write(&ut, innhold)?;
|
std::fs::write(&ut, innhold)?;
|
||||||
println!("Skrev {}", ut.display());
|
println!("Skrev {}", ut.display());
|
||||||
for m in &merknader {
|
for m in &merknader {
|
||||||
@@ -283,6 +292,36 @@ fn import(a: Import) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Bygningsbeskrivelsen som TOML, med et hode som sier hvor den kommer fra.
|
||||||
|
fn skriv_bygning(
|
||||||
|
bygning: &enok::modell::Bygning,
|
||||||
|
merknader: &[String],
|
||||||
|
fil: &Path,
|
||||||
|
tillegg: Option<&Path>,
|
||||||
|
) -> Result<String> {
|
||||||
|
let tekst = toml::to_string_pretty(bygning).context("serialiserer bygningen til TOML")?;
|
||||||
|
let mut innhold = match tillegg {
|
||||||
|
Some(t) => format!(
|
||||||
|
"# Generert av enok fra «{}» og tilleggsfilen «{}».\n\
|
||||||
|
# Rett i modellen eller i tilleggsfilen; denne filen skrives på nytt ved hver import.\n\
|
||||||
|
# Merknader fra importen:\n",
|
||||||
|
fil.display(),
|
||||||
|
t.display()
|
||||||
|
),
|
||||||
|
None => format!(
|
||||||
|
"# Generert av enok fra «{}».\n\
|
||||||
|
# Merknader fra importen:\n",
|
||||||
|
fil.display()
|
||||||
|
),
|
||||||
|
};
|
||||||
|
for m in merknader {
|
||||||
|
innhold.push_str(&format!("# {m}\n"));
|
||||||
|
}
|
||||||
|
innhold.push('\n');
|
||||||
|
innhold.push_str(&tekst);
|
||||||
|
Ok(innhold)
|
||||||
|
}
|
||||||
|
|
||||||
fn klima(a: Klima) -> Result<()> {
|
fn klima(a: Klima) -> Result<()> {
|
||||||
if a.liste {
|
if a.liste {
|
||||||
for k in enok::klima::tmy::kommuner()? {
|
for k in enok::klima::tmy::kommuner()? {
|
||||||
|
|||||||
+23
-6
@@ -218,7 +218,7 @@ impl Lost {
|
|||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
for v in &b.vindu {
|
for v in &b.vindu {
|
||||||
flater.push(los_vindu(v, &mut f));
|
flater.push(los_vindu(v, periode, &mut f));
|
||||||
}
|
}
|
||||||
if flater.is_empty() {
|
if flater.is_empty() {
|
||||||
bail!("bygningen har ingen flater; legg inn minst én [[flate]]");
|
bail!("bygningen har ingen flater; legg inn minst én [[flate]]");
|
||||||
@@ -646,8 +646,25 @@ fn los_flate(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn los_vindu(v: &Vindu, logg: &mut Vec<Forutsetning>) -> LostFlate {
|
fn los_vindu(
|
||||||
|
v: &Vindu,
|
||||||
|
periode: Option<&crate::data::Periode>,
|
||||||
|
logg: &mut Vec<Forutsetning>,
|
||||||
|
) -> LostFlate {
|
||||||
let areal = v.areal * v.antall;
|
let areal = v.areal * v.antall;
|
||||||
|
// U-verdi: oppgitt, eller typisk for byggeperioden. Et vindu uten oppgitt U-verdi er
|
||||||
|
// vanligvis originalvinduet, og da sier byggeåret mer enn en fast standardverdi.
|
||||||
|
let u = v.u_verdi.unwrap_or_else(|| {
|
||||||
|
let antatt = periode.map(|p| p.vindu).unwrap_or(2.8);
|
||||||
|
logg.push(Forutsetning {
|
||||||
|
felt: format!("vindu.{}.u_verdi", v.navn),
|
||||||
|
verdi: format!("{antatt:.2} W/(m²K)"),
|
||||||
|
kilde: periode
|
||||||
|
.map(|p| format!("typisk for {}", p.navn))
|
||||||
|
.unwrap_or_else(|| "antatt".into()),
|
||||||
|
});
|
||||||
|
antatt
|
||||||
|
});
|
||||||
let karm = v.karmandel.unwrap_or_else(|| {
|
let karm = v.karmandel.unwrap_or_else(|| {
|
||||||
logg.push(Forutsetning {
|
logg.push(Forutsetning {
|
||||||
felt: format!("vindu.{}.karmandel", v.navn),
|
felt: format!("vindu.{}.karmandel", v.navn),
|
||||||
@@ -660,11 +677,11 @@ fn los_vindu(v: &Vindu, logg: &mut Vec<Forutsetning>) -> LostFlate {
|
|||||||
0.0
|
0.0
|
||||||
} else {
|
} else {
|
||||||
v.g_verdi.unwrap_or_else(|| {
|
v.g_verdi.unwrap_or_else(|| {
|
||||||
let antatt = if v.u_verdi <= 1.2 { 0.50 } else { 0.75 };
|
let antatt = if u <= 1.2 { 0.50 } else { 0.75 };
|
||||||
logg.push(Forutsetning {
|
logg.push(Forutsetning {
|
||||||
felt: format!("vindu.{}.g_verdi", v.navn),
|
felt: format!("vindu.{}.g_verdi", v.navn),
|
||||||
verdi: format!("{antatt:.2}"),
|
verdi: format!("{antatt:.2}"),
|
||||||
kilde: if v.u_verdi <= 1.2 {
|
kilde: if u <= 1.2 {
|
||||||
"antatt 3-lags energiglass"
|
"antatt 3-lags energiglass"
|
||||||
} else {
|
} else {
|
||||||
"antatt 2-lags glass"
|
"antatt 2-lags glass"
|
||||||
@@ -688,8 +705,8 @@ fn los_vindu(v: &Vindu, logg: &mut Vec<Forutsetning>) -> LostFlate {
|
|||||||
navn: v.navn.clone(),
|
navn: v.navn.clone(),
|
||||||
typ: FlateType::Vegg,
|
typ: FlateType::Vegg,
|
||||||
areal,
|
areal,
|
||||||
u_verdi: v.u_verdi,
|
u_verdi: u,
|
||||||
u_konstruksjon: v.u_verdi,
|
u_konstruksjon: u,
|
||||||
helning: v.helning,
|
helning: v.helning,
|
||||||
orientering: v.orientering,
|
orientering: v.orientering,
|
||||||
kappa: 0.0,
|
kappa: 0.0,
|
||||||
|
|||||||
+3
-1
@@ -268,7 +268,9 @@ pub struct Vindu {
|
|||||||
pub orientering: f64,
|
pub orientering: f64,
|
||||||
#[serde(default = "nitti")]
|
#[serde(default = "nitti")]
|
||||||
pub helning: f64,
|
pub helning: f64,
|
||||||
pub u_verdi: f64,
|
/// U-verdi inkl. karm, W/(m²K). Utelatt: typisk for byggeåret.
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub u_verdi: Option<f64>,
|
||||||
/// Solfaktor for glasset (g-verdi). Utelatt: 0,75 for 2-lags, 0,5 for 3-lags energiglass.
|
/// Solfaktor for glasset (g-verdi). Utelatt: 0,75 for 2-lags, 0,5 for 3-lags energiglass.
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub g_verdi: Option<f64>,
|
pub g_verdi: Option<f64>,
|
||||||
|
|||||||
Reference in New Issue
Block a user