Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95ba6c42b2
|
||
|
|
1cdbd494e2
|
||
|
|
8e9544dd24
|
||
|
|
cbb1cedc5b
|
||
|
|
1e5d4f05bf
|
||
|
|
133152742f
|
||
|
|
959db648a9
|
||
|
|
3dff2dc273
|
||
|
|
b19162d9a7
|
||
|
|
c89561b0f5
|
||
|
|
e3f37afe36
|
||
|
|
cabd8500ea
|
||
|
|
e7d77547ac
|
Generated
+1
-1
@@ -222,7 +222,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "enok"
|
||||
version = "0.4.0"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"approx",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "enok"
|
||||
version = "0.4.0"
|
||||
version = "0.5.0"
|
||||
edition = "2021"
|
||||
description = "Årlig energiberegning og energirapport for boliger etter NS 3031 (timesmetode, ISO 52016-1)"
|
||||
license = "MIT"
|
||||
|
||||
@@ -180,6 +180,16 @@ navn = "Nye vinduer"
|
||||
endringer.vindu = [{ navn = "Vinduer sør", u_verdi = 0.80, g_verdi = 0.50 }]
|
||||
```
|
||||
|
||||
Rapporten kan følge husets eller firmaets egne dokumenter:
|
||||
|
||||
```toml
|
||||
[rapport]
|
||||
logo = "logo.svg" # vises øverst på forsiden
|
||||
farge = "#880000" # aksentfarge i overskrifter og tabeller
|
||||
avsender = "Firma · Adresse · Org.nr. 000 000 000"
|
||||
skrift_overskrift = "Futura Now Headline"
|
||||
```
|
||||
|
||||
Scenarier flettes inn i bygningsbeskrivelsen: tabeller flettes rekursivt, elementer i lister
|
||||
matches på `navn` eller `type`, nye elementer legges til, og `slett = true` fjerner et element.
|
||||
Et navn med `*` treffer alle elementer det passer på, for eksempel `navn = "Yttervegg*"`.
|
||||
@@ -310,6 +320,19 @@ Mangler en bygningsdel U-verdi i modellen, sier importen hvilke det gjelder, og
|
||||
bruker en typisk verdi for byggeåret. Det gjelder også vinduer og dører, som i de fleste
|
||||
eksportene ikke har U-verdi.
|
||||
|
||||
Importen tåler noen vanlige feil i eksporten:
|
||||
|
||||
- **Deler som ligger dobbelt.** Legger eksporten den samme veggen eller det samme dekket i
|
||||
flere etasjer, telles den én gang. Av kopiene brukes den som hører til den etasjen høyden
|
||||
tilsier, og vinduene i de andre finner fortsatt veggen sin.
|
||||
- **Feil etasje.** Hva som er gulv mot grunnen, dekke over kjelleren eller etasjeskiller inne
|
||||
i bygningen, avgjøres av høyden geometrien har, ikke av hvilken etasje delen er lagt i.
|
||||
- **Terrengmodell utenfor bakkenivå.** Ligger terrenget over gulvet i første etasje, brukes
|
||||
det ikke, og importen sier fra. Oppgi da `over_terreng` på kjellerveggene.
|
||||
|
||||
Importen sier også hvilke deler som ikke er regnet med i klimaskjermen, som innervegger og
|
||||
etasjeskillere, slik at det er lett å se om noe mangler.
|
||||
|
||||
Med de åtte feltene på plass er importen fra IFC nær nok komplett til at beregningen står på
|
||||
modellen alene.
|
||||
|
||||
@@ -382,7 +405,7 @@ jobs:
|
||||
"$GITHUB_SERVER_URL/klingenbergbygg/enok/releases/download/$TAG/enok"
|
||||
chmod +x enok
|
||||
./enok import modell.ifc --tillegg prosjekt.toml --ut bygning.toml
|
||||
- uses: https://gitea.com/actions/upload-artifact@v4
|
||||
- uses: https://gitea.com/actions/upload-artifact@v3
|
||||
with:
|
||||
name: bygningsmodell
|
||||
path: |
|
||||
@@ -396,7 +419,7 @@ jobs:
|
||||
shell: fish {0}
|
||||
steps:
|
||||
- uses: https://gitea.com/actions/checkout@v6
|
||||
- uses: https://gitea.com/actions/download-artifact@v4
|
||||
- uses: https://gitea.com/actions/download-artifact@v3
|
||||
with:
|
||||
name: bygningsmodell
|
||||
- name: Lag rapporten
|
||||
@@ -421,22 +444,75 @@ Motoren er kontrollert mot uavhengige referanser:
|
||||
| Normerte energiposter for småhus | varmtvann 25,1, belysning 11,4, teknisk utstyr 17,5 kWh/(m²·år) |
|
||||
| Faste energiposter for yrkesbyggene | belysning, utstyr og varmtvann treffer energibudsjettene i kildene |
|
||||
|
||||
### Kjent avvik
|
||||
### Kontroll mot de publiserte referansebyggene
|
||||
|
||||
Standard Norges lanseringseksempel for NS 3031:2025 oppgir 46,2 kWh/(m²·år) romoppvarming og
|
||||
106,4 kWh/(m²·år) netto energibehov for TEK17-referansebygget beregnet med Simien 8. enok
|
||||
beregner 27,1 og 86,6 for den samme bygningen.
|
||||
Multiconsult for DiBK, «Grunnlag og forutsetninger for beregnede energirammer» (13.11.2015),
|
||||
gir geometri, komponentverdier og beregnet netto energibehov for bygningskategoriene som
|
||||
ligger til grunn for energirammene i TEK17, beregnet med Simien etter NS 3031:2014. Modellene
|
||||
er gjengitt i `eksempler/referansebygg/`, og `tests/referansebygg_2015.rs` regner dem om igjen:
|
||||
|
||||
Varmetapsbudsjettet, de normerte energipostene, solinnstrålingen og energibalansen stemmer
|
||||
hver for seg, så avviket ligger i hvor stor del av solvarmen som nyttiggjøres. NS 3031:2025
|
||||
Tillegg A er ikke offentlig tilgjengelig, så de normerte solfaktorene, skjermingsforutsetningene
|
||||
og timesprofilene Simien bruker, lar seg ikke etterprøve her. `tests/referansebygg.rs` låser
|
||||
dagens verdier slik at endringer i motoren blir synlige, og oppdateres når standardens
|
||||
forutsetninger er tilgjengelige.
|
||||
| Bygg | enok | Publisert | Avvik |
|
||||
| --- | ---: | ---: | ---: |
|
||||
| Småhus | 87,7 | 107,5 | −18,4 % |
|
||||
| Boligblokk | 79,7 | 91,8 | −13,1 % |
|
||||
| Barnehage | 81,1 | 132,0 | −38,6 % |
|
||||
| Kontorbygning | 93,9 | 115,3 | −18,6 % |
|
||||
| Skolebygning | 73,6 | 106,8 | −31,1 % |
|
||||
| Idrettsbygning | 111,1 | 144,3 | −23,0 % |
|
||||
| Kulturbygning | 74,2 | 128,0 | −42,0 % |
|
||||
| **Snitt** | | | **−26,4 %** |
|
||||
|
||||
Bruk derfor tallene til å sammenligne tiltak mot hverandre og til å se hvor varmen forsvinner.
|
||||
Til en energiattest som skal leveres til Enova må resultatet kontrolleres mot et validert
|
||||
beregningsprogram.
|
||||
Netto energibehov, kWh/(m²·år), Oslo-klima. Kjør `cargo test --release --test
|
||||
referansebygg_2015 -- --nocapture` for tabellen.
|
||||
|
||||
**enok regner gjennomgående for lavt.** Avviket er systematisk og går én vei. Dette er
|
||||
kontrollert, og selve beregningen ser ut til å være i orden:
|
||||
|
||||
- Varmetapstallet for småhuset blir 0,70 W/(m²K) mot publisert 0,69, så geometri og
|
||||
U-verdier i modellene er riktig gjengitt.
|
||||
- De faste postene, varmtvann, belysning og teknisk utstyr, treffer de normerte verdiene
|
||||
nøyaktig, og solinnstrålingen avviker under 1 % fra pvlib.
|
||||
- Endres en enkelt komponent, svarer beregningen som den skal: en U-verdi som gir 40 W/K mer
|
||||
varmetap, gir 105 % av det varmetapet tilsier over året.
|
||||
- En uavhengig bin-beregning rett av klimafilen, med NS 3031s internlaster og
|
||||
varmetapstallet for referansebygget, gir 51,5 kWh/(m²·år) romoppvarming. Under de samme
|
||||
forutsetningene gir enok 49,5, altså 4 % lavere, og forskjellen er varmekapasiteten og
|
||||
grunnkoblingen som bin-beregningen ser bort fra.
|
||||
|
||||
Avviket kommer dermed av hva som regnes som varmetilskudd, ikke av varmetapet. For
|
||||
TEK17-småhuset er de enkeltvis målt til:
|
||||
|
||||
| Varmetilskudd | Romoppvarming, kWh/(m²·år) |
|
||||
| --- | ---: |
|
||||
| Sol gjennom vinduene | 10,8 |
|
||||
| Vifteeffekt til lufta | 4,4 |
|
||||
| Systemtap fra tappevann | 5,0 |
|
||||
| Nattsenking 21 → 19 °C | 4,1 |
|
||||
|
||||
Til sammenligning oppgir Standard Norges lanseringseksempel for NS 3031:2025 46,2
|
||||
kWh/(m²·år) romoppvarming for et TEK17-småhus. Bin-beregningen med litt solvarme lander på
|
||||
43,8, altså nær det publiserte tallet, mens enok med alle tilskuddene over lander på 22,9.
|
||||
|
||||
Det ene punktet som er avklart, er systemtapet: energirammene er netto energibehov, som ikke
|
||||
skal avhenge av varmeanlegget. Referansebyggene settes derfor opp med
|
||||
`systemtap_som_tilskudd = false`, og det flyttet snittavviket fra −27,9 % til −26,4 %.
|
||||
ISO 52000-1 og NS 3031:2025 regner gjenvinnbart systemtap som internt varmetilskudd, så for
|
||||
et bygg som skal beskrive faktisk energibruk, står valget på `true`, som er standardverdien.
|
||||
|
||||
Luftmengdene forklarer 3–5 kWh/(m²·år) for yrkesbyggene: full tabellverdi i stedet for
|
||||
80 % for behovsstyring gir barnehagen 84,7 i stedet for 81,1. Resten, og særlig avviket for
|
||||
barnehage, skole og kulturbygg, ligger i normerte inndata vi ikke har: NS 3031 Tillegg A og
|
||||
tabell B.1 for disse kategoriene.
|
||||
|
||||
### Hva tallene kan brukes til
|
||||
|
||||
Sammenligning av tiltak mot hverandre, og å se hvor varmen forsvinner. Et systematisk avvik
|
||||
som dette slår omtrent likt ut på alle alternativene, så rekkefølgen mellom tiltak står seg.
|
||||
|
||||
Den absolutte energikarakteren gjør den ikke. En energiattest skal lages av en godkjent
|
||||
ekspert i Enovas system, med et beregningsprogram som er validert for formålet, og enok er
|
||||
ikke validert av noen. Bruk derfor karakteren her som en indikasjon, og kontroller den mot et
|
||||
validert program før den brukes til noe.
|
||||
|
||||
## Datagrunnlag
|
||||
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
Exported entities 10
|
||||
IfcBuilding 1
|
||||
IfcBuildingStorey 3
|
||||
IfcDoor 1
|
||||
IfcSite 1
|
||||
IfcWall 1
|
||||
IfcWindow 3
|
||||
|
||||
Warnings 233
|
||||
Object was not exported, because it belongs to an excluded phase. 55
|
||||
Object was not exported, because it belongs to an excluded phase. 178
|
||||
IFC GUID: 2j4Yjl2uv7sAeJeoHqLzOy Uuid: {AD122B6F-0B8E-47D8-AA13-A3247457D63C} 1
|
||||
IfcColumn IFC GUID: 1fliq0k71CkvF_mRWfcoAd Uuid: {69BECD00-B870-4CBB-93FE-C1B8299B22A7} 1
|
||||
IfcColumn IFC GUID: 1LiqBcL3T7HRT2rLj0iN41 Uuid: {55B342E6-5437-4745-B742-D55B40B17101} 1
|
||||
IfcColumn IFC GUID: 1SGckJVn9CNwTsAh2trrN3 Uuid: {5C426B93-7F12-4C5F-A776-2AB0B7D755C3} 1
|
||||
IfcFurniture IFC GUID: 07i2O$aInFKPL16JlimA0l Uuid: {07B0263F-912C-4F51-9541-193BECC0A02F} 1
|
||||
IfcFurniture IFC GUID: 0881MCh9XAu9b7ODJoGiBW Uuid: {0820158C-AC98-4AE0-9947-60D4F242C2E0} 1
|
||||
IfcFurniture IFC GUID: 0bkAxYHlb6HxlpRkisdGZO Uuid: {25B8AEE2-46F9-4647-BBF3-6EEB369D08D8} 1
|
||||
IfcFurniture IFC GUID: 0DdaYNJJj2AgGn$D1C$9e$ Uuid: {0D9E4897-4D3B-422A-A431-FCD04CFC9A3F} 1
|
||||
IfcFurniture IFC GUID: 0GCNozZfTC0x2AqfbyLGT$ Uuid: {10317CBD-8E97-4C03-B08A-D2997C55077F} 1
|
||||
IfcFurniture IFC GUID: 0ikhlLsLT9V9e3GeZr6bKD Uuid: {2CBABBD5-D957-497C-9A03-4288F51A550D} 1
|
||||
IfcFurniture IFC GUID: 0uJwVeXDf2Ae$lWC26mEld Uuid: {384FA7E8-84DA-422A-8FEF-80C086C0EBE7} 1
|
||||
IfcFurniture IFC GUID: 16Vi0lhy95$ArNBwqZB1Mx Uuid: {467EC02F-AFC2-45FC-AD57-2FAD232C15BB} 1
|
||||
IfcFurniture IFC GUID: 1cfXJq2WTEkf1E3AKOBPNx Uuid: {66A614F4-0A07-4EBA-904E-0CA5182D95FB} 1
|
||||
IfcFurniture IFC GUID: 1Hlrjn6afFYwEUOmqRybbR Uuid: {51BF5B71-1A4A-4F8B-A39E-630D1BF2595B} 1
|
||||
IfcFurniture IFC GUID: 1KkrEE3Br8NOdyeNFJ_04x Uuid: {54BB538E-0CBD-485D-89FC-A173D3F8013B} 1
|
||||
IfcFurniture IFC GUID: 1lZY1xG4D1vQ39Y33HFhZv Uuid: {6F8E207B-4043-41E5-A0C9-8830D13EB8F9} 1
|
||||
IfcFurniture IFC GUID: 1pILNCHHP3buWC53YI72n$ Uuid: {734955CC-4516-4397-880C-1438921C2C7F} 1
|
||||
IfcFurniture IFC GUID: 1T$HvvGBPBv8LPOCUPozPA Uuid: {5DFD1E79-40B6-4BE4-8559-60C799CBD64A} 1
|
||||
IfcFurniture IFC GUID: 1TTIAne1zFj8fBUDD_uCwZ Uuid: {5D7522B1-A01F-4FB4-8A4B-78D37EE0CEA3} 1
|
||||
IfcFurniture IFC GUID: 1XHmn4qxzD38xK0PcrbtEN Uuid: {61470C44-D3BF-4D0C-8ED4-0199B5977397} 1
|
||||
IfcFurniture IFC GUID: 1zk6f_iVT4nOcUJF2aN4nE Uuid: {7DB86A7E-B1F7-44C5-899E-4CF0A45C4C4E} 1
|
||||
IfcFurniture IFC GUID: 24DrY7XwnBjg1ajG0eIf_I Uuid: {84375887-87AC-4BB6-A064-B500284A9F92} 1
|
||||
IfcFurniture IFC GUID: 28V7kvBB543eb7i_y_X3RS Uuid: {887C7BB9-2CB1-440E-8947-B3EF3E8436DC} 1
|
||||
IfcFurniture IFC GUID: 2DdhKSgrTCZApSm3H_sFCD Uuid: {8D9EB51C-AB57-4C8C-ACDC-C0347ED8F30D} 1
|
||||
IfcFurniture IFC GUID: 2f3EyoPZL9AA3TmLvysd4O Uuid: {A90CEF32-6635-4928-A0DD-C15E7CDA7118} 1
|
||||
IfcFurniture IFC GUID: 2Op4EchD1DjhrHD$posAgv Uuid: {98CC43A6-ACD0-4DB6-BD51-37FCF2D8AAB9} 1
|
||||
IfcFurniture IFC GUID: 2t53ebDxX8IRqP3RhvSlDa Uuid: {B7143A25-37B8-4849-BD19-0DBAF972F364} 1
|
||||
IfcFurniture IFC GUID: 32DWUyO6L5q8sG8C9mIBd7 Uuid: {C23607BC-6065-45D0-8D90-20C27048B9C7} 1
|
||||
IfcFurniture IFC GUID: 3B7VHvrSv1j8tZ$IvGVnew Uuid: {CB1DF479-D5CE-41B4-8DE3-FD2E507F1A3A} 1
|
||||
IfcFurniture IFC GUID: 3LhPbhSO95I8r5giBkMTY9 Uuid: {D5AD996B-7182-4548-8D45-AAC2EE59D889} 1
|
||||
IfcFurniture IFC GUID: 3tBET3kwP5hQ3qXJDeFaGF Uuid: {F72CE743-BBA6-45AD-A0F4-8533683E440F} 1
|
||||
IfcFurniture IFC GUID: 3VuLLdIwz64BofO0GqriXB Uuid: {DFE15567-4BAF-4610-BCA9-600434D6C84B} 1
|
||||
IfcFurniture IFC GUID: 3VynC$RT16qx_qG0fBkA_b Uuid: {DFF3133F-6DD0-46D3-BFB4-400A4BB8AFA5} 1
|
||||
IfcFurniture IFC GUID: 3ylIyQzeLFug2xXgCoMPs7 Uuid: {FCBD2F1A-F685-4FE2-A0BB-86A332599D87} 1
|
||||
IfcRailing IFC GUID: 3og_HkvSHECgyd9k9__l6B Uuid: {F2ABE46E-E5C4-4E32-AF27-26E27EFAF18B} 1
|
||||
IfcRoof IFC GUID: 3fJ48mjj5EhuM7zh$IBhSO Uuid: {E94C4230-B6D1-4EAF-8587-F6BFD22EB718} 1
|
||||
IfcSanitaryTerminal IFC GUID: 1yp7yRIff1tO8dFDKhlLlB Uuid: {7CCC7F1B-4A9A-41DD-8227-3CD52BBD5BCB} 1
|
||||
IfcSanitaryTerminal IFC GUID: 3fK_Pl8kz0leAbJ2cH0B7x Uuid: {E953E66F-22EF-40BE-82A5-4C299100B1FB} 1
|
||||
IfcSlab IFC GUID: 07WSyapvrC1xDeRL0MNUFy Uuid: {0781CF24-CF9D-4C07-B368-6D50165DE3FC} 1
|
||||
IfcSlab IFC GUID: 0b7APUG_HFs8YjQbZfhell Uuid: {251CA65E-43E4-4FD8-88AD-6A58E9AE8BEF} 1
|
||||
IfcSlab IFC GUID: 0MBiUrI$91xB0CQiMk$BVo Uuid: {162EC7B5-4BF2-41EC-B00C-6AC5AEFCB7F2} 1
|
||||
IfcSlab IFC GUID: 0N3tCvYin8TuisG5BsOPy_ Uuid: {170F7339-8ACC-4877-8B36-4052F6619F3E} 1
|
||||
IfcSlab IFC GUID: 0trzognO50txskx6BnXPYy Uuid: {37D7DCAA-C581-40DF-BDAE-EC62F18598BC} 1
|
||||
IfcSlab IFC GUID: 13vwcqdIT1jg7$huALfsZ0 Uuid: {43E7A9B4-9D27-41B6-A1FF-AF8295A768C0} 1
|
||||
IfcSlab IFC GUID: 14UX849r15EvLBl6SufiwR Uuid: {447A1204-2750-453B-954B-BC6738A6CE9B} 1
|
||||
IfcSlab IFC GUID: 19dxmR2dj9KPUH$it9udCS Uuid: {499FBC1B-0A7B-4951-9791-FECDC9E2731C} 1
|
||||
IfcSlab IFC GUID: 1D$pzp6zXCtB8QJLmd_SaH Uuid: {4DFF3F73-1BD8-4CDC-B21A-4D5C27F9C911} 1
|
||||
IfcSlab IFC GUID: 1nAMi735j5wfH4nAMDuPiD Uuid: {71296B07-0C5B-45EA-9444-C4A58DE19B0D} 1
|
||||
IfcSlab IFC GUID: 2vnxgFvyLEPuNXMWZCX6VU Uuid: {B9C7BA8F-E7C5-4E67-85E1-5A08CC8467DE} 1
|
||||
IfcSlab IFC GUID: 3nS3eMCPP6eeCeLd_Dh21d Uuid: {F1703A16-3196-46A2-8328-567F8DAC2067} 1
|
||||
IfcSpace IFC GUID: 0dwvJCIuPDIus0RmdkWAuo Uuid: {27EB94CC-4B86-4D4B-8D80-6F09EE80AE32} 1
|
||||
IfcSpace IFC GUID: 0qa$ZWkLb6RQBL4tIH2EeF Uuid: {3493F8E0-B959-466D-A2D5-13749108EA0F} 1
|
||||
IfcSpace IFC GUID: 0usL7gvbLCvhlmAATYEcze Uuid: {38D951EA-E655-4CE6-BBF0-28A7623A6F68} 1
|
||||
IfcSpace IFC GUID: 0WVjr0$FL7UQmfdHSTSFPm Uuid: {207EDD40-FCF5-4779-AC29-9D171D70F670} 1
|
||||
IfcSpace IFC GUID: 0ZhDNFKnf3ewzYK6IEMAz2 Uuid: {23ACD5CF-531A-43A3-AF62-50648E58AF42} 1
|
||||
IfcSpace IFC GUID: 1gKdzkjYjCHOvt3BXrQuYS Uuid: {6A527F6E-B62B-4C45-8E77-0CB8756B889C} 1
|
||||
IfcSpace IFC GUID: 1i1xAQbUPBkgkgldj4PJaM Uuid: {6C07B29A-95E6-4BBA-ABAA-BE7B44653916} 1
|
||||
IfcSpace IFC GUID: 1iqXr6MHTAhApQyP9ABqGt Uuid: {6CD21D46-5917-4AAC-ACDA-F1924A2F4437} 1
|
||||
IfcSpace IFC GUID: 1mjz_b_l9689hXrLvA_qCF Uuid: {70B7DFA5-FAF2-4620-9AE1-D55E4AFB430F} 1
|
||||
IfcSpace IFC GUID: 1Rz76e1Ur2LeeYek8vPOpj Uuid: {5BF471A8-05ED-4256-8A22-A2E239658CED} 1
|
||||
IfcSpace IFC GUID: 1vPuSAYJXEyvEh6_lfanzU Uuid: {7967870A-8938-4EF3-93AB-1BEBE9931F5E} 1
|
||||
IfcSpace IFC GUID: 1wlwZ24mHDMA0SuaP2weC8 Uuid: {7ABFA8C2-1304-4D58-A01C-E24642EA8308} 1
|
||||
IfcSpace IFC GUID: 1ZczeQg_LFHOVnxdRtnvBp Uuid: {639BDA1A-ABE5-4F45-87F1-EE76F7C792F3} 1
|
||||
IfcSpace IFC GUID: 2bvP5SFC99rAnCjiB3Vax7 Uuid: {A5E5915C-3CC2-49D4-AC4C-B6C2C37E4EC7} 1
|
||||
IfcSpace IFC GUID: 2ISw8ATsD889cHLclxPMrB Uuid: {9273A20A-7763-4820-9991-566BFB656D4B} 1
|
||||
IfcSpace IFC GUID: 2SL_maT5PD7fTXfWmMND02 Uuid: {9C57EC24-7456-4D1E-9761-A60C165CD002} 1
|
||||
IfcSpace IFC GUID: 2Wo7U6wCL9AvGd0$ILQCDN Uuid: {A0C87786-E8C5-492B-9427-03F49568C357} 1
|
||||
IfcSpace IFC GUID: 2zfleS6QL0zhwE$RSCjkDr Uuid: {BDA6FA1C-19A5-40F6-BE8E-FDB70CB6E375} 1
|
||||
IfcSpace IFC GUID: 3AflZrmK9C$8PKEtkGO_ML Uuid: {CAA6F8F5-C142-4CFC-8654-3B7B9063E595} 1
|
||||
IfcSpace IFC GUID: 3CeXrio2zCEPt$KBvdFpmF Uuid: {CCA21D6C-C82F-4C39-9DFF-50BE673F3C0F} 1
|
||||
IfcSpace IFC GUID: 3CMUtjbUf2VQXEOd6JcDdg Uuid: {CC59EDED-95EA-427D-A84E-62719398D9EA} 1
|
||||
IfcSpace IFC GUID: 3Gh4YW4F1AUR7DPEkB$S0y Uuid: {D0AC48A0-10F0-4A79-B1CD-64EB8BFDC03C} 1
|
||||
IfcSpace IFC GUID: 3tyuOb6mHBJe73AW673PJC Uuid: {F7F38625-1B04-4B4E-81C3-2A01870D94CC} 1
|
||||
IfcStair IFC GUID: 0d9nk8JXHFz9MplvJSaVN1 Uuid: {27271B88-4E14-4FF4-95B3-BF94DC91F5C1} 1
|
||||
IfcStair IFC GUID: 2Gm90FvwP5dQy5ZCcQN5$Y Uuid: {90C0900F-E7A6-459D-AF05-8CC99A5C5FE2} 1
|
||||
IfcStair IFC GUID: 2j4Yjl2uv7sAeJeoHqLzOy Uuid: {AD122B6F-0B8E-47D8-AA13-A3247457D63C} 1
|
||||
IfcSystemFurnitureElement IFC GUID: 3_6Dkn0FL1DPC3gj4oI9_9 Uuid: {FE18DBB1-00F5-4135-9303-AAD132489F89} 1
|
||||
IfcWall IFC GUID: 011fIifbf5UgD0m1Qs22ji Uuid: {010694AC-A65A-457A-A340-C016B6082B6C} 1
|
||||
IfcWall IFC GUID: 02hbt$XObCMhjMiw8qYvzd Uuid: {02AE5DFF-8589-4C5A-BB56-B3A2348B9F67} 1
|
||||
IfcWall IFC GUID: 02XNHduN50YuESKrAx$_nO Uuid: {02857467-E171-408B-839C-5352BBFFEC58} 1
|
||||
IfcWall IFC GUID: 04LdJolI5APgS8PG8J4osy Uuid: {045674F2-BD21-4A66-A708-650213132DBC} 1
|
||||
IfcWall IFC GUID: 08B2OV3LP3Yht2keSZOd95 Uuid: {082C261F-0D56-438A-BDC2-BA8723627245} 1
|
||||
IfcWall IFC GUID: 08YvDPgmL918r349XGIpF_ Uuid: {088B9359-AB05-4904-8D43-1098504B33FE} 1
|
||||
IfcWall IFC GUID: 0aQby46hz1ofJr7lINZUgh Uuid: {246A5F04-1ABF-41CA-94F5-1EF4978DEAAB} 1
|
||||
IfcWall IFC GUID: 0CF9aUjkf8xR8g8bhnHdYt Uuid: {0C3C991E-B6EA-48ED-B22A-225AF14678B7} 1
|
||||
IfcWall IFC GUID: 0DwscUXi9ErhcYX8Rl53mD Uuid: {0DEB699E-86C2-4ED6-B9A2-8486EF143C0D} 1
|
||||
IfcWall IFC GUID: 0hfSpV4gnC7f6M4o4Rle_n Uuid: {2BA5CCDF-12AC-4C1E-9196-13211BBE8FB1} 1
|
||||
IfcWall IFC GUID: 0I2icW51fAUQFRDh11gpFT Uuid: {120AC9A0-141A-4A79-A3DB-36B041AB33DD} 1
|
||||
IfcWall IFC GUID: 0jkimVUM16_9yuxUsCGS6c Uuid: {2DBACC1F-7960-46F8-9F38-EDED8C41C1A6} 1
|
||||
IfcWall IFC GUID: 0KLWSwjcDAa9H04VNlq1XS Uuid: {1456073A-B663-4A90-9440-11F5EFD0185C} 1
|
||||
IfcWall IFC GUID: 0LzcILtAzChPEDa6RC58AU Uuid: {15F66495-DCAF-4CAD-938D-9066CC14829E} 1
|
||||
IfcWall IFC GUID: 0m0RH0XkD2Puhx2cT$y7JF Uuid: {3001B440-86E3-4267-8AFB-0A677FF074CF} 1
|
||||
IfcWall IFC GUID: 0m3q1eK6X9oPySmHfZ0nf3 Uuid: {300F4068-5068-49C9-9F1C-C11A63031A43} 1
|
||||
IfcWall IFC GUID: 0MrV9CxDn9PvsyUZsIE9ri Uuid: {16D5F24C-ECDC-4967-9DBC-7A3D92389D6C} 1
|
||||
IfcWall IFC GUID: 0n60zBeln5KOZPA574pf50 Uuid: {31180F4B-A2FC-4551-88D9-2851C4CE9140} 1
|
||||
IfcWall IFC GUID: 0ocqH4R$1Ed9vkkxG4d1_E Uuid: {329B4444-6FF0-4E9C-9E6E-BBB4049C1F8E} 1
|
||||
IfcWall IFC GUID: 0oT03VbwP2uh9BVBoxo7sp Uuid: {327400DF-97A6-42E2-B24B-7CBCBBC87DB3} 1
|
||||
IfcWall IFC GUID: 0QXfLvMOzEmOLWs2JGF3jr Uuid: {1A869579-598F-4EC1-8560-D824D03C3B75} 1
|
||||
IfcWall IFC GUID: 0S2f2$43f1OxT3sf7frdES Uuid: {1C0A90BF-103A-4163-B743-DA91E9D6739C} 1
|
||||
IfcWall IFC GUID: 0tK0htVT55gOJEVTJ6disc Uuid: {37500AF7-7DD1-45A9-84CE-7DD4C69ECDA6} 1
|
||||
IfcWall IFC GUID: 0uzkec7W90cghnoiyRcp63 Uuid: {38F6EA26-1E02-409A-AAF1-CACF1B9B3183} 1
|
||||
IfcWall IFC GUID: 0w9FhDQDf8vg_4uNleaP_4 Uuid: {3A24FACD-68DA-48E6-AF84-E17BE8919F84} 1
|
||||
IfcWall IFC GUID: 0yV2Wuo095KvmjzYPiGboU Uuid: {3C7C2838-C802-4553-9C2D-F6266C425C9E} 1
|
||||
IfcWall IFC GUID: 1$stJY5wb5mBVEIKlc4uyS Uuid: {7FDB74E2-17A9-45C0-B7CE-494BE6138F1C} 1
|
||||
IfcWall IFC GUID: 11RKbz6q11A8Cw0lePezyh Uuid: {416D497D-1B40-4128-833A-02FA19A3DF2B} 1
|
||||
IfcWall IFC GUID: 13LryRtC53dfQnBC9whwW9 Uuid: {43575F1B-DCC1-439E-96B1-2CC27AAFA809} 1
|
||||
IfcWall IFC GUID: 14h9RrHSbFI8FWYNYQI2AE Uuid: {44AC96F5-45C9-4F48-83E0-89789A48228E} 1
|
||||
IfcWall IFC GUID: 16G9$5Xjn7hOfRsMYVpdte Uuid: {46409FC5-86DC-47AD-8A5B-D9689FCE7DE8} 1
|
||||
IfcWall IFC GUID: 18sNyWNPv27As03Mr45n$Q Uuid: {48D97F20-5D9E-421C-AD80-0D6D44171FDA} 1
|
||||
IfcWall IFC GUID: 18vDJap959aeAOjEVJRuBN Uuid: {48E4D4E4-CC91-4992-8298-B4E7D36F82D7} 1
|
||||
IfcWall IFC GUID: 1AxKOyS4j92fopMhhjpIAO Uuid: {4AED463C-704B-490A-9CB3-5ABAEDCD2298} 1
|
||||
IfcWall IFC GUID: 1bBTC9vJ52Ggqk1hdeQNbq Uuid: {652DD309-E531-4242-AD2E-06B9E8697974} 1
|
||||
IfcWall IFC GUID: 1d1zMVqdP7Ee6PHmbP725o Uuid: {6707D59F-D276-473A-8199-4709591C2172} 1
|
||||
IfcWall IFC GUID: 1Dg5OAQtf6hO$vY0cufbsP Uuid: {4DA8560A-6B7A-46AD-8FF9-8809B8A65D99} 1
|
||||
IfcWall IFC GUID: 1fa24_JofDF8iVG8ytsCOa Uuid: {6990213E-4F2A-4D3C-8B1F-408F37D8C624} 1
|
||||
IfcWall IFC GUID: 1Fs72RH1PF58caUTkuC4vS Uuid: {4FD8709B-4416-4F14-89A4-79DBB8304E5C} 1
|
||||
IfcWall IFC GUID: 1IPo9dHJb1WQTgQ88HDYtp Uuid: {52672267-4539-4181-A76A-688211362DF3} 1
|
||||
IfcWall IFC GUID: 1lEd3sjuD5RQKIBJeTekta Uuid: {6F3A70F6-B783-456D-A512-2D3A1DA2EDE4} 1
|
||||
IfcWall IFC GUID: 1mPD6JNVH8mBcZ15CIkpNa Uuid: {7064D193-5DF4-48C0-B9A3-045312BB35E4} 1
|
||||
IfcWall IFC GUID: 1N$wEw8o941vlEeTZwiDY6 Uuid: {57FFA3BA-2322-4407-9BCE-A1D8FAB0D886} 1
|
||||
IfcWall IFC GUID: 1O1dHRR8L61ewoIaqPnn4R Uuid: {5806745B-6C85-4606-8EB2-4A4D19C7111B} 1
|
||||
IfcWall IFC GUID: 1p_S6HkW93_f8HGgqt4yMg Uuid: {73F9C191-BA02-43FA-9211-42AD3713C5AA} 1
|
||||
IfcWall IFC GUID: 1Pnfun7Yr46wZEEuAHGalE Uuid: {59C69E31-1E2D-441B-A8CE-3B8291424BCE} 1
|
||||
IfcWall IFC GUID: 1poPXhDi9CwBw25OSe0LtO Uuid: {73C9986B-36C2-4CE8-BE82-158728015DD8} 1
|
||||
IfcWall IFC GUID: 1q1GX0aLDBlg$K09nBI5ZY Uuid: {74050840-9153-4BBE-AFD4-009C4B4858E2} 1
|
||||
IfcWall IFC GUID: 1uGlLfKcPBdRSVLJXY0BtQ Uuid: {7842F569-5266-4B9D-B71F-55386200BDDA} 1
|
||||
IfcWall IFC GUID: 1Uuo_cb4D0oAvfq8EVz8la Uuid: {5EE32FA6-9443-40C8-AE69-D0839FF48BE4} 1
|
||||
IfcWall IFC GUID: 1Uy_mHl35FtRbl3EuuzLvp Uuid: {5EF3EC11-BC31-4FDD-B96F-0CEE38F55E73} 1
|
||||
IfcWall IFC GUID: 1W9OwQ4jn83O68vfXjAYc5 Uuid: {60258E9A-12DC-480D-8188-E6986D2A2985} 1
|
||||
IfcWall IFC GUID: 1WbCjyS9DCPvvkJF7rtT2V Uuid: {6094CB7C-7093-4C67-9E6E-4CF1F5DDD09F} 1
|
||||
IfcWall IFC GUID: 20XqXEyqb0wxwmfO1Q7$_r Uuid: {8087484E-F349-40EB-BEB0-A5805A1FFFB5} 1
|
||||
IfcWall IFC GUID: 24xi793Ub3NuS1WwIbBuUW Uuid: {84EEC1C9-0DE9-435F-8701-83A4A52F87A0} 1
|
||||
IfcWall IFC GUID: 25cdmVbMr2tekzO__aeP1K Uuid: {859A7C1F-956D-42DE-8BBD-63EFA4A19054} 1
|
||||
IfcWall IFC GUID: 26vkWJTKr7yPnAkyzn1whT Uuid: {86E6E813-754D-47F1-9C4A-BBCF7107AADD} 1
|
||||
IfcWall IFC GUID: 27K4SwxDf7CQTo_bdD1ita Uuid: {8750473A-ECDA-4731-A772-FA59CD06CDE4} 1
|
||||
IfcWall IFC GUID: 2_TkJTwoTE4Ri0B5ws4gOy Uuid: {BE76E4DD-EB27-4E11-BB00-2C5EB612A63C} 1
|
||||
IfcWall IFC GUID: 2C0FlaHcr4bPJ6ivLUAxwf Uuid: {8C00FBE4-466D-4495-94C6-B3955E2BBEA9} 1
|
||||
IfcWall IFC GUID: 2dAQd57XnAN8wcPs2xrIr9 Uuid: {A729A9C5-1E1C-4A5C-8EA6-6760BBD52D49} 1
|
||||
IfcWall IFC GUID: 2EPNtdYV50AO9il_nn3StI Uuid: {8E657DE7-89F1-4029-826C-BFEC710DCDD2} 1
|
||||
IfcWall IFC GUID: 2FzPwSYSj0nO1lmZRq9dy0 Uuid: {8FF59E9C-89CB-40C5-806F-C236F4267F00} 1
|
||||
IfcWall IFC GUID: 2GE8t9jZT4mPIU_Cex_u5C Uuid: {90388DC9-B637-44C1-949E-F8CA3BFB814C} 1
|
||||
IfcWall IFC GUID: 2h9nahZ4D6o84C_zaietNq Uuid: {AB27192B-8C43-46C8-810C-FBD92CA375F4} 1
|
||||
IfcWall IFC GUID: 2Hd6ParQDDO8b6P3WvsCvk Uuid: {919C6664-D5A3-4D60-8946-643839D8CE6E} 1
|
||||
IfcWall IFC GUID: 2KBkO13kbFO8vxYftMUhop Uuid: {942EE601-0EE9-4F60-8E7B-8A9DD67ABCB3} 1
|
||||
IfcWall IFC GUID: 2KXJG437XFeRghjfUAuwe6 Uuid: {94853404-0C78-4FA1-BAAB-B6978AE3AA06} 1
|
||||
IfcWall IFC GUID: 2NJLfDIaHE0xy4hUHI88Q8 Uuid: {974D5A4D-4A44-4E03-BF04-ADE452208688} 1
|
||||
IfcWall IFC GUID: 2pkmaeTon3mP841EO$XKr_ Uuid: {B3BB0928-772C-43C1-9204-04E63F854D7E} 1
|
||||
IfcWall IFC GUID: 2qnL88IP18KOC46_zM$3Q6 Uuid: {B4C55208-4990-4851-8304-1BEF56FC3686} 1
|
||||
IfcWall IFC GUID: 2QPqhAlwv5z8fnxL7FclU3 Uuid: {9A674ACA-BFAE-45F4-8A71-ED51CF9AF783} 1
|
||||
IfcWall IFC GUID: 2wTrE5Qr95XvTYD_pf4efL Uuid: {BA775385-6B52-4587-9762-37ECE9128A55} 1
|
||||
IfcWall IFC GUID: 2XlScs8fz0vgAXQK$RjwGp Uuid: {A1BDC9B6-229F-40E6-A2A1-694FDBB7A433} 1
|
||||
IfcWall IFC GUID: 33f9lwxs5C$gzA_tv8Q_ty Uuid: {C3A49BFA-EF61-4CFE-AF4A-FB7E486BEDFC} 1
|
||||
IfcWall IFC GUID: 34pktMu3vB1fqQG6Kdp0wt Uuid: {C4CEEDD6-E03E-4B06-9D1A-406527CC0EB7} 1
|
||||
IfcWall IFC GUID: 36tKbQYvf1igzF3NzYcukf Uuid: {C6DD495A-8B9A-41B2-AF4F-0D7F629B8BA9} 1
|
||||
IfcWall IFC GUID: 37l0C1$6n5FORQUe6hzilq Uuid: {C7BC0301-FC6C-453D-86DA-7A81ABF6CBF4} 1
|
||||
IfcWall IFC GUID: 38XL6G2IL6BOFb9_hicIy$ Uuid: {C8855190-0925-462D-83E5-27EAEC992F3F} 1
|
||||
IfcWall IFC GUID: 3B0ibynjT0ZwbBLjydckcJ Uuid: {CB02C97C-C6D7-408F-A94B-56DF279AE993} 1
|
||||
IfcWall IFC GUID: 3FGDF39YvDMP_dWSedAdf1 Uuid: {CF40D3C3-262E-4D59-9FA7-81CA272A7A41} 1
|
||||
IfcWall IFC GUID: 3GGCttPXP9691A4fYqYPVS Uuid: {D040CDF7-6616-4918-904A-1298B48997DC} 1
|
||||
IfcWall IFC GUID: 3H_C$m8hHEM9GeEJKFROVn Uuid: {D1F8CFF0-22B4-4E58-9428-39350F6D87F1} 1
|
||||
IfcWall IFC GUID: 3J6PC0OvjFZv2JopH5$HYo Uuid: {D3199300-639B-4F8F-9093-CB3445FD18B2} 1
|
||||
IfcWall IFC GUID: 3kH8pDoSv3RwPJWxMAg8Cf Uuid: {EE448CCD-C9CE-436F-A653-83B58AA88329} 1
|
||||
IfcWall IFC GUID: 3LLVg6vfH9Fg0TOjWfPjSE Uuid: {D555FA86-E694-493E-A01D-62D82966D70E} 1
|
||||
IfcWall IFC GUID: 3Lt8TYxN54TfuAQi8WlGVW Uuid: {D5DC8762-ED71-4476-9E0A-6AC220BD07E0} 1
|
||||
IfcWall IFC GUID: 3lyfiiGsbEhO3r_B9BGd5E Uuid: {EFF29B2C-4369-4EAD-80F5-F8B24B42714E} 1
|
||||
IfcWall IFC GUID: 3mPddsu3PFMB4_fMci6SmG Uuid: {F06679F6-E036-4F58-B13E-A569AC19CC10} 1
|
||||
IfcWall IFC GUID: 3oWKwr91D9yu9735ujDBCa Uuid: {F2814EB5-2413-49F3-8247-0C5E2D34B324} 1
|
||||
IfcWall IFC GUID: 3p_eBcR25BUgLRUoU6ZSnU Uuid: {F3FA82E6-6C21-4B7A-A55B-7B27868DCC5E} 1
|
||||
IfcWall IFC GUID: 3QjKbLZ_98h8RV_F9ogZY7 Uuid: {DAB54955-8FE2-48AC-86DF-F8F272AA3887} 1
|
||||
IfcWall IFC GUID: 3Qne0sjAnA0hQIQyS31ndY Uuid: {DAC68036-B4AC-4A02-B692-6BC7030719E2} 1
|
||||
IfcWall IFC GUID: 3rqsGKaSTAuAEQWrJR4N1_ Uuid: {F5D36414-91C7-4AE0-A39A-8354DB11707E} 1
|
||||
IfcWall IFC GUID: 3ToDumCrT09RkujRr0n7RV Uuid: {DDC8DE30-3357-4025-BBB8-B5BD40C476DF} 1
|
||||
IfcWall IFC GUID: 3VLKXM2CP7BuXDwuPkca7E Uuid: {DF554856-08C6-472F-884D-EB866E9A41CE} 1
|
||||
IfcWall IFC GUID: 3w4c71wE12$88mIOeAWyZf Uuid: {FA1261C1-E8E0-42FC-8230-498A0A83C8E9} 1
|
||||
IfcWall IFC GUID: 3y0_1eYoLBtga8fxTCRIjM Uuid: {FC03E068-8B25-4BDE-A908-A7B74C6D2B56} 1
|
||||
IfcWall IFC GUID: 3ybT5jnE59UR352P6n2wLg Uuid: {FC95D16D-C4E1-4979-B0C5-0991B10BA56A} 1
|
||||
IfcWall IFC GUID: 3YKPwIInjEWx9bpZjWJpmq Uuid: {E2519E92-4B1B-4E83-B265-CE3B604F3C34} 1
|
||||
IfcWall IFC GUID: 3YqWhqTnP7JvnIq8aGASpZ Uuid: {E2D20AF4-7716-474F-9C52-D0891029CCE3} 1
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
# Barnehage — referansebygg for energirammene i TEK17.
|
||||
#
|
||||
# Geometri og komponentverdier fra Multiconsult for DiBK, «Grunnlag og forutsetninger for
|
||||
# beregnede energirammer» (128298-RIEn-NOT-001, 13. november 2015), tabell 1 og 2. Notatet
|
||||
# oppgir beregnet netto energibehov 132.0 kWh/(m²·år) for denne modellen, beregnet
|
||||
# med Simien etter NS 3031:2014. Filen er enoks gjengivelse av den samme modellen.
|
||||
#
|
||||
# Forenklinger: taket er regnet flatt, og veggarealet er omkrets × etasjehøyde × antall
|
||||
# etasjer minus vindusarealet.
|
||||
|
||||
[bygning]
|
||||
navn = "Barnehage (referansebygg TEK17)"
|
||||
kategori = "barnehage"
|
||||
byggeaar = 2017
|
||||
oppvarmet_bra = 300.0
|
||||
oppvarmet_volum = 960.0
|
||||
|
||||
[klima]
|
||||
kommune = "Oslo"
|
||||
|
||||
[[flate]]
|
||||
navn = "Yttervegger"
|
||||
type = "vegg"
|
||||
areal = 205.0
|
||||
u_verdi = 0.18
|
||||
masseklasse = "middels"
|
||||
|
||||
[[flate]]
|
||||
navn = "Tak"
|
||||
type = "tak"
|
||||
areal = 300.0
|
||||
u_verdi = 0.13
|
||||
masseklasse = "middels"
|
||||
|
||||
[[flate]]
|
||||
navn = "Gulv på grunn"
|
||||
type = "gulv"
|
||||
areal = 300.0
|
||||
u_verdi = 0.10
|
||||
mot = "grunn"
|
||||
omkrets = 80.0
|
||||
masseklasse = "tung"
|
||||
kommentar = "U-verdien i notatet er oppgitt inkludert varmemotstanden i grunnen"
|
||||
|
||||
[[vindu]]
|
||||
navn = "Vinduer nord"
|
||||
areal = 22.5
|
||||
orientering = 0
|
||||
u_verdi = 0.80
|
||||
g_verdi = 0.40
|
||||
karmandel = 0.20
|
||||
|
||||
[[vindu]]
|
||||
navn = "Vinduer øst"
|
||||
areal = 15.0
|
||||
orientering = 90
|
||||
u_verdi = 0.80
|
||||
g_verdi = 0.40
|
||||
karmandel = 0.20
|
||||
|
||||
[[vindu]]
|
||||
navn = "Vinduer sør"
|
||||
areal = 22.5
|
||||
orientering = 180
|
||||
u_verdi = 0.80
|
||||
g_verdi = 0.40
|
||||
karmandel = 0.20
|
||||
|
||||
[[vindu]]
|
||||
navn = "Vinduer vest"
|
||||
areal = 15.0
|
||||
orientering = 270
|
||||
u_verdi = 0.80
|
||||
g_verdi = 0.40
|
||||
karmandel = 0.20
|
||||
|
||||
[kuldebroer]
|
||||
normalisert = 0.07
|
||||
|
||||
[infiltrasjon]
|
||||
n50 = 0.6
|
||||
skjerming = "moderat"
|
||||
|
||||
[ventilasjon]
|
||||
type = "balansert"
|
||||
luftmengde = 8.0
|
||||
gjenvinning = 0.85
|
||||
sfp = 1.5
|
||||
|
||||
[drift]
|
||||
# Energirammene er regnet som netto energibehov, uavhengig av varmeanlegget, så
|
||||
# systemtapet fra tappevann og varmedistribusjon teller ikke som varmetilskudd her.
|
||||
systemtap_som_tilskudd = false
|
||||
belysning_effekt = 6.4
|
||||
|
||||
[distribusjon]
|
||||
type = "punkt"
|
||||
|
||||
[[oppvarming.kilde]]
|
||||
type = "direkte_el"
|
||||
andel_rom = 1.0
|
||||
andel_tappevann = 1.0
|
||||
@@ -0,0 +1,86 @@
|
||||
# Boligblokk — referansebygg for energirammene i TEK17.
|
||||
#
|
||||
# Geometri og komponentverdier fra Multiconsult for DiBK, «Grunnlag og forutsetninger for
|
||||
# beregnede energirammer» (128298-RIEn-NOT-001, 13. november 2015), tabell 1 og 2. Notatet
|
||||
# oppgir beregnet netto energibehov 91.8 kWh/(m²·år) for denne modellen, beregnet
|
||||
# med Simien etter NS 3031:2014. Filen er enoks gjengivelse av den samme modellen.
|
||||
#
|
||||
# Forenklinger: taket er regnet flatt, og veggarealet er omkrets × etasjehøyde × antall
|
||||
# etasjer minus vindusarealet.
|
||||
|
||||
[bygning]
|
||||
navn = "Boligblokk (referansebygg TEK17)"
|
||||
kategori = "boligblokk"
|
||||
byggeaar = 2017
|
||||
oppvarmet_bra = 900.0
|
||||
oppvarmet_volum = 2430.0
|
||||
|
||||
[klima]
|
||||
kommune = "Oslo"
|
||||
|
||||
[[flate]]
|
||||
navn = "Yttervegger"
|
||||
type = "vegg"
|
||||
areal = 495.0
|
||||
u_verdi = 0.18
|
||||
masseklasse = "middels"
|
||||
|
||||
[[flate]]
|
||||
navn = "Tak"
|
||||
type = "tak"
|
||||
areal = 300.0
|
||||
u_verdi = 0.13
|
||||
masseklasse = "middels"
|
||||
|
||||
[[flate]]
|
||||
navn = "Gulv på grunn"
|
||||
type = "gulv"
|
||||
areal = 300.0
|
||||
u_verdi = 0.10
|
||||
mot = "grunn"
|
||||
omkrets = 80.0
|
||||
masseklasse = "tung"
|
||||
kommentar = "U-verdien i notatet er oppgitt inkludert varmemotstanden i grunnen"
|
||||
|
||||
[[vindu]]
|
||||
navn = "Vinduer nord"
|
||||
areal = 112.5
|
||||
orientering = 0
|
||||
u_verdi = 0.80
|
||||
g_verdi = 0.40
|
||||
karmandel = 0.20
|
||||
|
||||
[[vindu]]
|
||||
navn = "Vinduer sør"
|
||||
areal = 112.5
|
||||
orientering = 180
|
||||
u_verdi = 0.80
|
||||
g_verdi = 0.40
|
||||
karmandel = 0.20
|
||||
|
||||
[kuldebroer]
|
||||
normalisert = 0.07
|
||||
|
||||
[infiltrasjon]
|
||||
n50 = 0.6
|
||||
skjerming = "moderat"
|
||||
|
||||
[ventilasjon]
|
||||
type = "balansert"
|
||||
luftmengde = 1.5
|
||||
gjenvinning = 0.8
|
||||
sfp = 1.5
|
||||
|
||||
[drift]
|
||||
# Energirammene er regnet som netto energibehov, uavhengig av varmeanlegget, så
|
||||
# systemtapet fra tappevann og varmedistribusjon teller ikke som varmetilskudd her.
|
||||
systemtap_som_tilskudd = false
|
||||
belysning_effekt = 1.95
|
||||
|
||||
[distribusjon]
|
||||
type = "punkt"
|
||||
|
||||
[[oppvarming.kilde]]
|
||||
type = "direkte_el"
|
||||
andel_rom = 1.0
|
||||
andel_tappevann = 1.0
|
||||
@@ -0,0 +1,107 @@
|
||||
# Idrettsbygning — referansebygg for energirammene i TEK17.
|
||||
#
|
||||
# Geometri og komponentverdier fra Multiconsult for DiBK, «Grunnlag og forutsetninger for
|
||||
# beregnede energirammer» (128298-RIEn-NOT-001, 13. november 2015), tabell 1 og 2. Notatet
|
||||
# oppgir beregnet netto energibehov 144.3 kWh/(m²·år) for denne modellen, beregnet
|
||||
# med Simien etter NS 3031:2014. Filen er enoks gjengivelse av den samme modellen.
|
||||
#
|
||||
# Forenklinger: taket er regnet flatt, og veggarealet er omkrets × etasjehøyde × antall
|
||||
# etasjer minus vindusarealet.
|
||||
|
||||
[bygning]
|
||||
navn = "Idrettsbygning (referansebygg TEK17)"
|
||||
kategori = "idrettsbygg"
|
||||
byggeaar = 2017
|
||||
oppvarmet_bra = 3200.0
|
||||
oppvarmet_volum = 25600.0
|
||||
|
||||
[klima]
|
||||
kommune = "Oslo"
|
||||
|
||||
[[flate]]
|
||||
navn = "Yttervegger"
|
||||
type = "vegg"
|
||||
areal = 1192.0
|
||||
u_verdi = 0.18
|
||||
masseklasse = "middels"
|
||||
|
||||
[[flate]]
|
||||
navn = "Tak"
|
||||
type = "tak"
|
||||
areal = 3200.0
|
||||
u_verdi = 0.13
|
||||
masseklasse = "middels"
|
||||
|
||||
[[flate]]
|
||||
navn = "Gulv på grunn"
|
||||
type = "gulv"
|
||||
areal = 3200.0
|
||||
u_verdi = 0.10
|
||||
mot = "grunn"
|
||||
omkrets = 240.0
|
||||
masseklasse = "tung"
|
||||
kommentar = "U-verdien i notatet er oppgitt inkludert varmemotstanden i grunnen"
|
||||
|
||||
[[vindu]]
|
||||
navn = "Vinduer nord"
|
||||
areal = 240.0
|
||||
orientering = 0
|
||||
u_verdi = 0.80
|
||||
g_verdi = 0.40
|
||||
karmandel = 0.20
|
||||
solskjerming = { type = "automatisk", faktor = 0.125 }
|
||||
|
||||
[[vindu]]
|
||||
navn = "Vinduer øst"
|
||||
areal = 160.0
|
||||
orientering = 90
|
||||
u_verdi = 0.80
|
||||
g_verdi = 0.40
|
||||
karmandel = 0.20
|
||||
solskjerming = { type = "automatisk", faktor = 0.125 }
|
||||
|
||||
[[vindu]]
|
||||
navn = "Vinduer sør"
|
||||
areal = 240.0
|
||||
orientering = 180
|
||||
u_verdi = 0.80
|
||||
g_verdi = 0.40
|
||||
karmandel = 0.20
|
||||
solskjerming = { type = "automatisk", faktor = 0.125 }
|
||||
|
||||
[[vindu]]
|
||||
navn = "Vinduer vest"
|
||||
areal = 160.0
|
||||
orientering = 270
|
||||
u_verdi = 0.80
|
||||
g_verdi = 0.40
|
||||
karmandel = 0.20
|
||||
solskjerming = { type = "automatisk", faktor = 0.125 }
|
||||
|
||||
[kuldebroer]
|
||||
normalisert = 0.07
|
||||
|
||||
[infiltrasjon]
|
||||
n50 = 0.6
|
||||
skjerming = "moderat"
|
||||
|
||||
[ventilasjon]
|
||||
type = "balansert"
|
||||
luftmengde = 6.4
|
||||
gjenvinning = 0.85
|
||||
sfp = 1.5
|
||||
kjolebatteri = true
|
||||
|
||||
[drift]
|
||||
# Energirammene er regnet som netto energibehov, uavhengig av varmeanlegget, så
|
||||
# systemtapet fra tappevann og varmedistribusjon teller ikke som varmetilskudd her.
|
||||
systemtap_som_tilskudd = false
|
||||
belysning_effekt = 6.4
|
||||
|
||||
[distribusjon]
|
||||
type = "radiator"
|
||||
|
||||
[[oppvarming.kilde]]
|
||||
type = "direkte_el"
|
||||
andel_rom = 1.0
|
||||
andel_tappevann = 1.0
|
||||
@@ -0,0 +1,107 @@
|
||||
# Kontorbygning — referansebygg for energirammene i TEK17.
|
||||
#
|
||||
# Geometri og komponentverdier fra Multiconsult for DiBK, «Grunnlag og forutsetninger for
|
||||
# beregnede energirammer» (128298-RIEn-NOT-001, 13. november 2015), tabell 1 og 2. Notatet
|
||||
# oppgir beregnet netto energibehov 115.3 kWh/(m²·år) for denne modellen, beregnet
|
||||
# med Simien etter NS 3031:2014. Filen er enoks gjengivelse av den samme modellen.
|
||||
#
|
||||
# Forenklinger: taket er regnet flatt, og veggarealet er omkrets × etasjehøyde × antall
|
||||
# etasjer minus vindusarealet.
|
||||
|
||||
[bygning]
|
||||
navn = "Kontorbygning (referansebygg TEK17)"
|
||||
kategori = "kontorbygg"
|
||||
byggeaar = 2017
|
||||
oppvarmet_bra = 3600.0
|
||||
oppvarmet_volum = 11520.0
|
||||
|
||||
[klima]
|
||||
kommune = "Oslo"
|
||||
|
||||
[[flate]]
|
||||
navn = "Yttervegger"
|
||||
type = "vegg"
|
||||
areal = 780.0
|
||||
u_verdi = 0.18
|
||||
masseklasse = "middels"
|
||||
|
||||
[[flate]]
|
||||
navn = "Tak"
|
||||
type = "tak"
|
||||
areal = 1200.0
|
||||
u_verdi = 0.13
|
||||
masseklasse = "middels"
|
||||
|
||||
[[flate]]
|
||||
navn = "Gulv på grunn"
|
||||
type = "gulv"
|
||||
areal = 1200.0
|
||||
u_verdi = 0.10
|
||||
mot = "grunn"
|
||||
omkrets = 160.0
|
||||
masseklasse = "tung"
|
||||
kommentar = "U-verdien i notatet er oppgitt inkludert varmemotstanden i grunnen"
|
||||
|
||||
[[vindu]]
|
||||
navn = "Vinduer nord"
|
||||
areal = 270.0
|
||||
orientering = 0
|
||||
u_verdi = 0.80
|
||||
g_verdi = 0.40
|
||||
karmandel = 0.20
|
||||
solskjerming = { type = "automatisk", faktor = 0.125 }
|
||||
|
||||
[[vindu]]
|
||||
navn = "Vinduer øst"
|
||||
areal = 180.0
|
||||
orientering = 90
|
||||
u_verdi = 0.80
|
||||
g_verdi = 0.40
|
||||
karmandel = 0.20
|
||||
solskjerming = { type = "automatisk", faktor = 0.125 }
|
||||
|
||||
[[vindu]]
|
||||
navn = "Vinduer sør"
|
||||
areal = 270.0
|
||||
orientering = 180
|
||||
u_verdi = 0.80
|
||||
g_verdi = 0.40
|
||||
karmandel = 0.20
|
||||
solskjerming = { type = "automatisk", faktor = 0.125 }
|
||||
|
||||
[[vindu]]
|
||||
navn = "Vinduer vest"
|
||||
areal = 180.0
|
||||
orientering = 270
|
||||
u_verdi = 0.80
|
||||
g_verdi = 0.40
|
||||
karmandel = 0.20
|
||||
solskjerming = { type = "automatisk", faktor = 0.125 }
|
||||
|
||||
[kuldebroer]
|
||||
normalisert = 0.07
|
||||
|
||||
[infiltrasjon]
|
||||
n50 = 0.6
|
||||
skjerming = "moderat"
|
||||
|
||||
[ventilasjon]
|
||||
type = "balansert"
|
||||
luftmengde = 8.0
|
||||
gjenvinning = 0.85
|
||||
sfp = 1.5
|
||||
kjolebatteri = true
|
||||
|
||||
[drift]
|
||||
# Energirammene er regnet som netto energibehov, uavhengig av varmeanlegget, så
|
||||
# systemtapet fra tappevann og varmedistribusjon teller ikke som varmetilskudd her.
|
||||
systemtap_som_tilskudd = false
|
||||
belysning_effekt = 6.4
|
||||
|
||||
[distribusjon]
|
||||
type = "radiator"
|
||||
|
||||
[[oppvarming.kilde]]
|
||||
type = "direkte_el"
|
||||
andel_rom = 1.0
|
||||
andel_tappevann = 1.0
|
||||
@@ -0,0 +1,107 @@
|
||||
# Kulturbygning — referansebygg for energirammene i TEK17.
|
||||
#
|
||||
# Geometri og komponentverdier fra Multiconsult for DiBK, «Grunnlag og forutsetninger for
|
||||
# beregnede energirammer» (128298-RIEn-NOT-001, 13. november 2015), tabell 1 og 2. Notatet
|
||||
# oppgir beregnet netto energibehov 128.0 kWh/(m²·år) for denne modellen, beregnet
|
||||
# med Simien etter NS 3031:2014. Filen er enoks gjengivelse av den samme modellen.
|
||||
#
|
||||
# Forenklinger: taket er regnet flatt, og veggarealet er omkrets × etasjehøyde × antall
|
||||
# etasjer minus vindusarealet.
|
||||
|
||||
[bygning]
|
||||
navn = "Kulturbygning (referansebygg TEK17)"
|
||||
kategori = "kulturbygg"
|
||||
byggeaar = 2017
|
||||
oppvarmet_bra = 2400.0
|
||||
oppvarmet_volum = 7680.0
|
||||
|
||||
[klima]
|
||||
kommune = "Oslo"
|
||||
|
||||
[[flate]]
|
||||
navn = "Yttervegger"
|
||||
type = "vegg"
|
||||
areal = 520.0
|
||||
u_verdi = 0.18
|
||||
masseklasse = "middels"
|
||||
|
||||
[[flate]]
|
||||
navn = "Tak"
|
||||
type = "tak"
|
||||
areal = 1200.0
|
||||
u_verdi = 0.13
|
||||
masseklasse = "middels"
|
||||
|
||||
[[flate]]
|
||||
navn = "Gulv på grunn"
|
||||
type = "gulv"
|
||||
areal = 1200.0
|
||||
u_verdi = 0.10
|
||||
mot = "grunn"
|
||||
omkrets = 160.0
|
||||
masseklasse = "tung"
|
||||
kommentar = "U-verdien i notatet er oppgitt inkludert varmemotstanden i grunnen"
|
||||
|
||||
[[vindu]]
|
||||
navn = "Vinduer nord"
|
||||
areal = 180.0
|
||||
orientering = 0
|
||||
u_verdi = 0.80
|
||||
g_verdi = 0.40
|
||||
karmandel = 0.20
|
||||
solskjerming = { type = "automatisk", faktor = 0.125 }
|
||||
|
||||
[[vindu]]
|
||||
navn = "Vinduer øst"
|
||||
areal = 120.0
|
||||
orientering = 90
|
||||
u_verdi = 0.80
|
||||
g_verdi = 0.40
|
||||
karmandel = 0.20
|
||||
solskjerming = { type = "automatisk", faktor = 0.125 }
|
||||
|
||||
[[vindu]]
|
||||
navn = "Vinduer sør"
|
||||
areal = 180.0
|
||||
orientering = 180
|
||||
u_verdi = 0.80
|
||||
g_verdi = 0.40
|
||||
karmandel = 0.20
|
||||
solskjerming = { type = "automatisk", faktor = 0.125 }
|
||||
|
||||
[[vindu]]
|
||||
navn = "Vinduer vest"
|
||||
areal = 120.0
|
||||
orientering = 270
|
||||
u_verdi = 0.80
|
||||
g_verdi = 0.40
|
||||
karmandel = 0.20
|
||||
solskjerming = { type = "automatisk", faktor = 0.125 }
|
||||
|
||||
[kuldebroer]
|
||||
normalisert = 0.07
|
||||
|
||||
[infiltrasjon]
|
||||
n50 = 0.6
|
||||
skjerming = "moderat"
|
||||
|
||||
[ventilasjon]
|
||||
type = "balansert"
|
||||
luftmengde = 8.0
|
||||
gjenvinning = 0.85
|
||||
sfp = 1.5
|
||||
kjolebatteri = true
|
||||
|
||||
[drift]
|
||||
# Energirammene er regnet som netto energibehov, uavhengig av varmeanlegget, så
|
||||
# systemtapet fra tappevann og varmedistribusjon teller ikke som varmetilskudd her.
|
||||
systemtap_som_tilskudd = false
|
||||
belysning_effekt = 6.4
|
||||
|
||||
[distribusjon]
|
||||
type = "radiator"
|
||||
|
||||
[[oppvarming.kilde]]
|
||||
type = "direkte_el"
|
||||
andel_rom = 1.0
|
||||
andel_tappevann = 1.0
|
||||
@@ -0,0 +1,107 @@
|
||||
# Skolebygning — referansebygg for energirammene i TEK17.
|
||||
#
|
||||
# Geometri og komponentverdier fra Multiconsult for DiBK, «Grunnlag og forutsetninger for
|
||||
# beregnede energirammer» (128298-RIEn-NOT-001, 13. november 2015), tabell 1 og 2. Notatet
|
||||
# oppgir beregnet netto energibehov 106.8 kWh/(m²·år) for denne modellen, beregnet
|
||||
# med Simien etter NS 3031:2014. Filen er enoks gjengivelse av den samme modellen.
|
||||
#
|
||||
# Forenklinger: taket er regnet flatt, og veggarealet er omkrets × etasjehøyde × antall
|
||||
# etasjer minus vindusarealet.
|
||||
|
||||
[bygning]
|
||||
navn = "Skolebygning (referansebygg TEK17)"
|
||||
kategori = "skolebygg"
|
||||
byggeaar = 2017
|
||||
oppvarmet_bra = 2400.0
|
||||
oppvarmet_volum = 7680.0
|
||||
|
||||
[klima]
|
||||
kommune = "Oslo"
|
||||
|
||||
[[flate]]
|
||||
navn = "Yttervegger"
|
||||
type = "vegg"
|
||||
areal = 520.0
|
||||
u_verdi = 0.18
|
||||
masseklasse = "middels"
|
||||
|
||||
[[flate]]
|
||||
navn = "Tak"
|
||||
type = "tak"
|
||||
areal = 1200.0
|
||||
u_verdi = 0.13
|
||||
masseklasse = "middels"
|
||||
|
||||
[[flate]]
|
||||
navn = "Gulv på grunn"
|
||||
type = "gulv"
|
||||
areal = 1200.0
|
||||
u_verdi = 0.10
|
||||
mot = "grunn"
|
||||
omkrets = 160.0
|
||||
masseklasse = "tung"
|
||||
kommentar = "U-verdien i notatet er oppgitt inkludert varmemotstanden i grunnen"
|
||||
|
||||
[[vindu]]
|
||||
navn = "Vinduer nord"
|
||||
areal = 180.0
|
||||
orientering = 0
|
||||
u_verdi = 0.80
|
||||
g_verdi = 0.40
|
||||
karmandel = 0.20
|
||||
solskjerming = { type = "automatisk", faktor = 0.125 }
|
||||
|
||||
[[vindu]]
|
||||
navn = "Vinduer øst"
|
||||
areal = 120.0
|
||||
orientering = 90
|
||||
u_verdi = 0.80
|
||||
g_verdi = 0.40
|
||||
karmandel = 0.20
|
||||
solskjerming = { type = "automatisk", faktor = 0.125 }
|
||||
|
||||
[[vindu]]
|
||||
navn = "Vinduer sør"
|
||||
areal = 180.0
|
||||
orientering = 180
|
||||
u_verdi = 0.80
|
||||
g_verdi = 0.40
|
||||
karmandel = 0.20
|
||||
solskjerming = { type = "automatisk", faktor = 0.125 }
|
||||
|
||||
[[vindu]]
|
||||
navn = "Vinduer vest"
|
||||
areal = 120.0
|
||||
orientering = 270
|
||||
u_verdi = 0.80
|
||||
g_verdi = 0.40
|
||||
karmandel = 0.20
|
||||
solskjerming = { type = "automatisk", faktor = 0.125 }
|
||||
|
||||
[kuldebroer]
|
||||
normalisert = 0.07
|
||||
|
||||
[infiltrasjon]
|
||||
n50 = 0.6
|
||||
skjerming = "moderat"
|
||||
|
||||
[ventilasjon]
|
||||
type = "balansert"
|
||||
luftmengde = 8.0
|
||||
gjenvinning = 0.85
|
||||
sfp = 1.5
|
||||
kjolebatteri = true
|
||||
|
||||
[drift]
|
||||
# Energirammene er regnet som netto energibehov, uavhengig av varmeanlegget, så
|
||||
# systemtapet fra tappevann og varmedistribusjon teller ikke som varmetilskudd her.
|
||||
systemtap_som_tilskudd = false
|
||||
belysning_effekt = 8.0
|
||||
|
||||
[distribusjon]
|
||||
type = "radiator"
|
||||
|
||||
[[oppvarming.kilde]]
|
||||
type = "direkte_el"
|
||||
andel_rom = 1.0
|
||||
andel_tappevann = 1.0
|
||||
@@ -0,0 +1,102 @@
|
||||
# Småhus — referansebygg for energirammene i TEK17.
|
||||
#
|
||||
# Geometri og komponentverdier fra Multiconsult for DiBK, «Grunnlag og forutsetninger for
|
||||
# beregnede energirammer» (128298-RIEn-NOT-001, 13. november 2015), tabell 1 og 2. Notatet
|
||||
# oppgir beregnet netto energibehov 107.5 kWh/(m²·år) for denne modellen, beregnet
|
||||
# med Simien etter NS 3031:2014. Filen er enoks gjengivelse av den samme modellen.
|
||||
#
|
||||
# Forenklinger: taket er regnet flatt, og veggarealet er omkrets × etasjehøyde × antall
|
||||
# etasjer minus vindusarealet.
|
||||
|
||||
[bygning]
|
||||
navn = "Småhus (referansebygg TEK17)"
|
||||
kategori = "smaahus"
|
||||
byggeaar = 2017
|
||||
oppvarmet_bra = 160.0
|
||||
oppvarmet_volum = 459.2
|
||||
|
||||
[klima]
|
||||
kommune = "Oslo"
|
||||
|
||||
[[flate]]
|
||||
navn = "Yttervegger"
|
||||
type = "vegg"
|
||||
areal = 188.2
|
||||
u_verdi = 0.18
|
||||
masseklasse = "middels"
|
||||
|
||||
[[flate]]
|
||||
navn = "Tak"
|
||||
type = "tak"
|
||||
areal = 80.0
|
||||
u_verdi = 0.13
|
||||
masseklasse = "middels"
|
||||
|
||||
[[flate]]
|
||||
navn = "Gulv på grunn"
|
||||
type = "gulv"
|
||||
areal = 80.0
|
||||
u_verdi = 0.10
|
||||
mot = "grunn"
|
||||
omkrets = 36.0
|
||||
masseklasse = "tung"
|
||||
kommentar = "U-verdien i notatet er oppgitt inkludert varmemotstanden i grunnen"
|
||||
|
||||
[[vindu]]
|
||||
navn = "Vinduer nord"
|
||||
areal = 12.0
|
||||
orientering = 0
|
||||
u_verdi = 0.80
|
||||
g_verdi = 0.40
|
||||
karmandel = 0.20
|
||||
|
||||
[[vindu]]
|
||||
navn = "Vinduer øst"
|
||||
areal = 8.0
|
||||
orientering = 90
|
||||
u_verdi = 0.80
|
||||
g_verdi = 0.40
|
||||
karmandel = 0.20
|
||||
|
||||
[[vindu]]
|
||||
navn = "Vinduer sør"
|
||||
areal = 12.0
|
||||
orientering = 180
|
||||
u_verdi = 0.80
|
||||
g_verdi = 0.40
|
||||
karmandel = 0.20
|
||||
|
||||
[[vindu]]
|
||||
navn = "Vinduer vest"
|
||||
areal = 8.0
|
||||
orientering = 270
|
||||
u_verdi = 0.80
|
||||
g_verdi = 0.40
|
||||
karmandel = 0.20
|
||||
|
||||
[kuldebroer]
|
||||
normalisert = 0.05
|
||||
|
||||
[infiltrasjon]
|
||||
n50 = 0.6
|
||||
skjerming = "moderat"
|
||||
|
||||
[ventilasjon]
|
||||
type = "balansert"
|
||||
luftmengde = 1.2
|
||||
gjenvinning = 0.8
|
||||
sfp = 1.5
|
||||
|
||||
[drift]
|
||||
# Energirammene er regnet som netto energibehov, uavhengig av varmeanlegget, så
|
||||
# systemtapet fra tappevann og varmedistribusjon teller ikke som varmetilskudd her.
|
||||
systemtap_som_tilskudd = false
|
||||
belysning_effekt = 1.95
|
||||
|
||||
[distribusjon]
|
||||
type = "punkt"
|
||||
|
||||
[[oppvarming.kilde]]
|
||||
type = "direkte_el"
|
||||
andel_rom = 1.0
|
||||
andel_tappevann = 1.0
|
||||
@@ -596,6 +596,7 @@ fn bygg(
|
||||
lufting: Lufting::default(),
|
||||
drift: Drift::default(),
|
||||
scenario: Vec::new(),
|
||||
rapport: Rapport::default(),
|
||||
};
|
||||
Ok(Importrapport { bygning, merknader })
|
||||
}
|
||||
|
||||
+285
-26
@@ -26,6 +26,9 @@ pub struct Importrapport {
|
||||
pub utelatt: Vec<(String, f64, Option<f64>)>,
|
||||
}
|
||||
|
||||
/// Konstruksjonsnavn og avrundet plassering, som skiller én bygningsdel fra en annen.
|
||||
type Dublettnokkel = (String, i64, i64, i64, i64);
|
||||
|
||||
const VEGGTYPER: [&str; 3] = ["IFCWALL", "IFCWALLSTANDARDCASE", "IFCCURTAINWALL"];
|
||||
const PLATETYPER: [&str; 3] = ["IFCSLAB", "IFCROOF", "IFCSLABSTANDARDCASE"];
|
||||
|
||||
@@ -182,9 +185,61 @@ pub fn fra_tekst_med(tekst: &str, standardnavn: &str, valg: &Importvalg) -> Resu
|
||||
plass,
|
||||
areal: flate.map(|f| f.areal).unwrap_or(0.0),
|
||||
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),
|
||||
topp: plass.map(|p| p.2).unwrap_or(0.0) + flate.map(|f| f.topp).unwrap_or(0.0),
|
||||
omkrets: flate.map(|f| f.omkrets()).unwrap_or(0.0),
|
||||
});
|
||||
}
|
||||
|
||||
// Noen eksporter legger den samme bygningsdelen i flere etasjer. To deler med samme
|
||||
// konstruksjon, samme areal og samme plassering er den samme veggen eller det samme
|
||||
// dekket. Kopiene blir stående, slik at vinduene i dem finner veggen sin, men de telles
|
||||
// ikke med i klimaskjermen. Av kopiene beholdes den som ligger i den etasjen høyden
|
||||
// tilsier, slik at en kjellervegg havner i kjelleren.
|
||||
let konstruksjon = |navn: &str| navn.split(" #").next().unwrap_or(navn).trim().to_string();
|
||||
let mut grupper: Vec<(Dublettnokkel, Vec<u64>)> = Vec::new();
|
||||
for d in &deler {
|
||||
let Some((x, y, _, _, _)) = d.plass else {
|
||||
continue;
|
||||
};
|
||||
let cm = |v: f64| (v * 50.0).round() as i64;
|
||||
let nokkel = (
|
||||
konstruksjon(&d.navn),
|
||||
cm(x),
|
||||
cm(y),
|
||||
cm(d.z),
|
||||
(d.areal * 100.0).round() as i64,
|
||||
);
|
||||
match grupper.iter_mut().find(|(k, _)| *k == nokkel) {
|
||||
Some((_, medlemmer)) => medlemmer.push(d.nr),
|
||||
None => grupper.push((nokkel, vec![d.nr])),
|
||||
}
|
||||
}
|
||||
let mut dublett_av: HashMap<u64, u64> = HashMap::new();
|
||||
for (_, medlemmer) in grupper.iter().filter(|(_, m)| m.len() > 1) {
|
||||
let avstand_til_etasjen = |nr: u64| {
|
||||
let d = deler.iter().find(|d| d.nr == nr)?;
|
||||
let hoyde = d.etasje.and_then(|i| etasjer.get(i)).map(|e| e.hoyde)?;
|
||||
Some((d.z - hoyde).abs())
|
||||
};
|
||||
let beholdt = *medlemmer
|
||||
.iter()
|
||||
.min_by(|a, b| {
|
||||
let (fa, fb) = (avstand_til_etasjen(**a), avstand_til_etasjen(**b));
|
||||
match (fa, fb) {
|
||||
(Some(x), Some(y)) => x.total_cmp(&y).then(a.cmp(b)),
|
||||
(Some(_), None) => std::cmp::Ordering::Less,
|
||||
(None, Some(_)) => std::cmp::Ordering::Greater,
|
||||
(None, None) => a.cmp(b),
|
||||
}
|
||||
})
|
||||
.unwrap_or(&medlemmer[0]);
|
||||
for nr in medlemmer.iter().filter(|nr| **nr != beholdt) {
|
||||
dublett_av.insert(*nr, beholdt);
|
||||
}
|
||||
}
|
||||
let dubletter = dublett_av.len();
|
||||
|
||||
// Tyngdepunkt av de ytre veggene bestemmer hvilken vei normalen peker.
|
||||
let ytre_deler: Vec<&Del> = deler
|
||||
.iter()
|
||||
@@ -233,9 +288,18 @@ pub fn fra_tekst_med(tekst: &str, standardnavn: &str, valg: &Importvalg) -> Resu
|
||||
// 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 terrengmodell = terrengpunkter(&fil, skala);
|
||||
let terreng = terrenghoyder(&fil, skala);
|
||||
let kjeller_over_terreng = match (terreng, forste_over.and_then(|i| etasjer.get(i))) {
|
||||
// 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.
|
||||
let terreng_over_gulvet = match (terreng, forste_over.and_then(|i| etasjer.get(i))) {
|
||||
(Some((referanse, terrengnivaa)), Some(forste)) => {
|
||||
terrengnivaa > referanse + forste.hoyde + 0.5
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
let kjeller_over_terreng = match (terreng, forste_over.and_then(|i| etasjer.get(i))) {
|
||||
(Some((referanse, terrengnivaa)), Some(forste)) if !terreng_over_gulvet => {
|
||||
over_terreng_for_kjeller(referanse, terrengnivaa, forste.hoyde, kjellerdybde)
|
||||
}
|
||||
_ => 0.0,
|
||||
@@ -247,18 +311,19 @@ pub fn fra_tekst_med(tekst: &str, standardnavn: &str, valg: &Importvalg) -> Resu
|
||||
let mut utelatt: Vec<(String, f64, Option<f64>)> = Vec::new();
|
||||
let mut uten_uverdi: Vec<String> = Vec::new();
|
||||
let mut uten_geometri = 0usize;
|
||||
let mut grunnmurshoyder: Vec<f64> = Vec::new();
|
||||
let mut asimut_for_vegg: HashMap<u64, f64> = HashMap::new();
|
||||
|
||||
// Laveste etasje og laveste gulvnivå skiller dekke mot grunn fra etasjeskillere.
|
||||
let etasjekontekst = Etasjekontekst {
|
||||
nederste: (!etasjer.is_empty()).then_some(0),
|
||||
kjeller: kjelleretasje,
|
||||
forste_over,
|
||||
forste_over_hoyde: forste_over.and_then(|i| etasjer.get(i)).map(|e| e.hoyde),
|
||||
};
|
||||
let laveste = deler
|
||||
.iter()
|
||||
.filter(|d| PLATETYPER.contains(&d.typ.as_str()))
|
||||
.filter_map(|d| d.plass.map(|p| p.2))
|
||||
.map(|d| d.z)
|
||||
.fold(f64::INFINITY, f64::min);
|
||||
|
||||
for d in &deler {
|
||||
@@ -271,16 +336,25 @@ pub fn fra_tekst_med(tekst: &str, standardnavn: &str, valg: &Importvalg) -> Resu
|
||||
uten_geometri += 1;
|
||||
continue;
|
||||
}
|
||||
if dublett_av.contains_key(&d.nr) {
|
||||
continue;
|
||||
}
|
||||
let i_kjeller = kjelleretasje.is_some() && d.etasje == kjelleretasje;
|
||||
let Some((typ, mot)) = klassifiser(d, laveste, helning, &etasjekontekst) else {
|
||||
utelatt.push((d.navn.clone(), d.areal, d.u_verdi));
|
||||
continue;
|
||||
};
|
||||
// En etasjeskiller i den laveste etasjen over terreng er grensen mot kjelleren.
|
||||
// En etasjeskiller like over kjelleren er grensen mot den uoppvarmede sonen. Høyden
|
||||
// avgjør, ikke etasjetilknytningen, som kan være feil i eksporten.
|
||||
let ved_kjellertaket = etasjekontekst
|
||||
.forste_over_hoyde
|
||||
.map(|h| (d.z - h).abs() < 0.6)
|
||||
.unwrap_or(false);
|
||||
let (mot, sone) = if kjelleretasje.is_some()
|
||||
&& !i_kjeller
|
||||
&& d.etasje == forste_over
|
||||
&& ved_kjellertaket
|
||||
&& typ == FlateType::Gulv
|
||||
&& mot != Mot::Grunn
|
||||
{
|
||||
(Mot::Uoppvarmet, Some(kjellernavn.clone()))
|
||||
} else {
|
||||
@@ -289,6 +363,17 @@ pub fn fra_tekst_med(tekst: &str, standardnavn: &str, valg: &Importvalg) -> Resu
|
||||
if d.u_verdi.is_none() {
|
||||
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 {
|
||||
&mut kjellerflater
|
||||
} else {
|
||||
@@ -307,9 +392,10 @@ pub fn fra_tekst_med(tekst: &str, standardnavn: &str, valg: &Importvalg) -> Resu
|
||||
mot,
|
||||
b_faktor: None,
|
||||
absorptans: None,
|
||||
omkrets: None,
|
||||
dybde: (i_kjeller && mot == Mot::Grunn).then_some(kjellerdybde - kjeller_over_terreng),
|
||||
over_terreng: (i_kjeller && mot == Mot::Grunn).then_some(kjeller_over_terreng),
|
||||
omkrets: (typ == FlateType::Gulv && mot == Mot::Grunn && d.omkrets > 0.0)
|
||||
.then_some(d.omkrets),
|
||||
dybde: (i_kjeller && mot == Mot::Grunn).then_some(kjellerdybde - over_bakken),
|
||||
over_terreng: (i_kjeller && mot == Mot::Grunn).then_some(over_bakken),
|
||||
sone,
|
||||
kommentar: d
|
||||
.etasje
|
||||
@@ -324,6 +410,9 @@ pub fn fra_tekst_med(tekst: &str, standardnavn: &str, valg: &Importvalg) -> Resu
|
||||
|
||||
// ── vinduer og dører ────────────────────────────────────────────
|
||||
let mut vinduer: Vec<Vindu> = Vec::new();
|
||||
let mut sette_vinduer: std::collections::HashSet<(String, (i64, i64, i64), i64)> =
|
||||
Default::default();
|
||||
let mut vindusdubletter = 0usize;
|
||||
for (r, e) in fil.av_type("IFCWINDOW").chain(fil.av_type("IFCDOOR")) {
|
||||
let dor = e.typ == "IFCDOOR";
|
||||
let hoyde = e.arg(8).and_then(|a| a.som_tall()).unwrap_or(0.0) * skala;
|
||||
@@ -340,7 +429,9 @@ pub fn fra_tekst_med(tekst: &str, standardnavn: &str, valg: &Importvalg) -> Resu
|
||||
dor_vert.get(&r)
|
||||
} else {
|
||||
vindu_vert.get(&r)
|
||||
};
|
||||
}
|
||||
.map(|v| dublett_av.get(v).copied().unwrap_or(*v));
|
||||
let vert = vert.as_ref();
|
||||
// Vinduer i innervegger hører ikke til klimaskjermen.
|
||||
let i_ytre_vegg = vert
|
||||
.and_then(|v| deler.iter().find(|d| d.nr == *v))
|
||||
@@ -354,6 +445,25 @@ pub fn fra_tekst_med(tekst: &str, standardnavn: &str, valg: &Importvalg) -> Resu
|
||||
));
|
||||
continue;
|
||||
}
|
||||
// Det samme vinduet i to kopier av den samme veggen er ett vindu.
|
||||
let sted = e
|
||||
.arg(5)
|
||||
.and_then(|a| a.som_ref())
|
||||
.and_then(|p| global_plassering(&fil, p, skala))
|
||||
.map(|(x, y, z, _, _)| {
|
||||
let cm = |v: f64| (v * 50.0).round() as i64;
|
||||
(cm(x), cm(y), cm(z))
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let nokkel = (
|
||||
konstruksjon(&navn_av(e, r, if dor { "Dør" } else { "Vindu" })),
|
||||
sted,
|
||||
(areal * 100.0).round() as i64,
|
||||
);
|
||||
if !sette_vinduer.insert(nokkel) {
|
||||
vindusdubletter += 1;
|
||||
continue;
|
||||
}
|
||||
let asimut = vert
|
||||
.and_then(|v| asimut_for_vegg.get(v))
|
||||
.copied()
|
||||
@@ -443,6 +553,44 @@ pub fn fra_tekst_med(tekst: &str, standardnavn: &str, valg: &Importvalg) -> Resu
|
||||
"{uten_geometri} bygningsdeler er utelatt fordi geometrien ikke ga noe areal."
|
||||
));
|
||||
}
|
||||
if !utelatt.is_empty() {
|
||||
// Gruppert per konstruksjon, slik at listen er til å lese.
|
||||
let mut grupper: Vec<(String, usize, f64)> = Vec::new();
|
||||
for (navn, areal, _) in &utelatt {
|
||||
let kort = navn.split(" #").next().unwrap_or(navn).trim().to_string();
|
||||
match grupper.iter_mut().find(|(n, _, _)| *n == kort) {
|
||||
Some((_, antall, sum)) => {
|
||||
*antall += 1;
|
||||
*sum += areal;
|
||||
}
|
||||
None => grupper.push((kort, 1, *areal)),
|
||||
}
|
||||
}
|
||||
grupper.sort_by(|a, b| b.2.total_cmp(&a.2));
|
||||
let liste: Vec<String> = grupper
|
||||
.iter()
|
||||
.take(10)
|
||||
.map(|(navn, antall, sum)| {
|
||||
if *antall > 1 {
|
||||
format!("{navn} ×{antall} ({sum:.0} m²)")
|
||||
} else {
|
||||
format!("{navn} ({sum:.0} m²)")
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
merknader.push(format!(
|
||||
"{} bygningsdeler er ikke regnet med i klimaskjermen, som innervegger og dekker inne i bygningen: {}{}.",
|
||||
utelatt.len(),
|
||||
liste.join(", "),
|
||||
if grupper.len() > liste.len() { " …" } else { "" }
|
||||
));
|
||||
}
|
||||
if dubletter + vindusdubletter > 0 {
|
||||
merknader.push(format!(
|
||||
"{} bygningsdeler ligger dobbelt i modellen, samme konstruksjon på samme sted, og er telt én gang. Det skjer når eksporten legger de samme delene i flere etasjer.",
|
||||
dubletter + vindusdubletter
|
||||
));
|
||||
}
|
||||
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.
|
||||
@@ -561,7 +709,27 @@ pub fn fra_tekst_med(tekst: &str, standardnavn: &str, valg: &Importvalg) -> Resu
|
||||
rom.join(", ")
|
||||
)),
|
||||
});
|
||||
let grunnmur = if terreng.is_some() {
|
||||
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!(
|
||||
"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 \
|
||||
terrengmodellen i bakkenivå, eller sett `over_terreng` i meter på veggene."
|
||||
)
|
||||
} else if terreng.is_some() {
|
||||
format!(
|
||||
"Etter terrenghøyden i modellen står grunnmuren {kjeller_over_terreng:.2} m over bakken og \
|
||||
{:.2} m under.",
|
||||
@@ -695,6 +863,7 @@ pub fn fra_tekst_med(tekst: &str, standardnavn: &str, valg: &Importvalg) -> Resu
|
||||
lufting: Lufting::default(),
|
||||
drift: Drift::default(),
|
||||
scenario: Vec::new(),
|
||||
rapport: Rapport::default(),
|
||||
};
|
||||
Ok(Importrapport {
|
||||
bygning,
|
||||
@@ -1085,6 +1254,45 @@ fn terrenghoyder(fil: &Fil, skala: f64) -> Option<(f64, f64)> {
|
||||
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.
|
||||
///
|
||||
/// Toppen av grunnmuren ligger ved gulvet i første etasje over kjelleren. Den står over
|
||||
@@ -1110,6 +1318,12 @@ struct Del {
|
||||
plass: Option<(f64, f64, f64, f64, f64)>,
|
||||
areal: f64,
|
||||
normal: Option<[f64; 3]>,
|
||||
/// Delens høyde over byggets nullpunkt, av geometrien, m.
|
||||
z: f64,
|
||||
/// Overkanten av delen over byggets nullpunkt, m. For en kjellervegg er det overkant mur.
|
||||
topp: f64,
|
||||
/// Omkretsen av delens omriss, m. Et gulv mot grunnen trenger den for B′.
|
||||
omkrets: f64,
|
||||
}
|
||||
|
||||
/// Oversetter en fritekstverdi fra modellen til en bygningskategori.
|
||||
@@ -1257,12 +1471,13 @@ fn retning_av(d: &Del, cx: f64, cy: f64, sant_nord: f64) -> (f64, f64) {
|
||||
/// Avgjør om en bygningsdel hører til klimaskjermen, og hva den vender mot.
|
||||
/// Etasjeinndelingen som klassifiseringen trenger.
|
||||
struct Etasjekontekst {
|
||||
/// Nederste etasje i modellen.
|
||||
nederste: Option<usize>,
|
||||
/// Etasjen under terreng, dersom modellen har en.
|
||||
kjeller: Option<usize>,
|
||||
/// Laveste etasje over terreng, der etasjeskilleren mot kjelleren ligger.
|
||||
forste_over: Option<usize>,
|
||||
/// Høyden på den etasjen, m. Geometrien veier tyngre enn etasjetilknytningen, som kan
|
||||
/// være feil i eksporten.
|
||||
forste_over_hoyde: Option<f64>,
|
||||
}
|
||||
|
||||
fn klassifiser(
|
||||
@@ -1297,20 +1512,26 @@ fn klassifiser(
|
||||
if d.ytre == Some(true) {
|
||||
return Some((FlateType::Gulv, Mot::Ute));
|
||||
}
|
||||
// Mot grunnen bare i nederste etasje. Er etasjen ukjent, brukes høyden.
|
||||
let nederst = match (d.etasje, e.nederste) {
|
||||
(Some(a), Some(b)) => a == b,
|
||||
_ => d
|
||||
.plass
|
||||
.map(|p| (p.2 - laveste).abs() < 0.5)
|
||||
.unwrap_or(false),
|
||||
};
|
||||
if nederst || navn.contains("kjellergulv") {
|
||||
// Høyden av geometrien 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
|
||||
// gulvet i første etasje himlingen over kjelleren, og det nederste dekket ligger mot
|
||||
// grunnen. «Kjellergulv» er ikke nok til å si at dekket ligger på bakken: dekket over
|
||||
// kjelleren bærer ofte det samme navnet.
|
||||
let paa_grunnen = ["on grade", "på grunn", "paa grunn", "såle", "sale"];
|
||||
if paa_grunnen.iter().any(|o| navn.contains(o)) {
|
||||
return Some((FlateType::Gulv, Mot::Grunn));
|
||||
}
|
||||
// Dekket i laveste etasje over en kjeller er grensen mot den uoppvarmede sonen.
|
||||
if e.kjeller.is_some() && d.etasje.is_some() && d.etasje == e.forste_over {
|
||||
return Some((FlateType::Gulv, Mot::Uoppvarmet));
|
||||
if e.kjeller.is_some() {
|
||||
let ved_forste_over = e
|
||||
.forste_over_hoyde
|
||||
.map(|h| (d.z - h).abs() < 0.6)
|
||||
.unwrap_or(false);
|
||||
if ved_forste_over || (d.etasje.is_some() && d.etasje == e.forste_over) {
|
||||
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.
|
||||
return None;
|
||||
@@ -1678,6 +1899,11 @@ mod tester {
|
||||
/// To yttervegger på 5 × 2,5 m med hver sin trekantgeometri, i millimeter.
|
||||
/// Sørveggen ligger ved y = −5 m, nordveggen ved y = +5 m.
|
||||
fn prove() -> String {
|
||||
prove_med(&[(100, "Sorvegg", -5.0), (120, "Nordvegg", 5.0)])
|
||||
}
|
||||
|
||||
/// Som `prove`, men med de veggene testen ber om: (entitetsnummer, navn, y i meter).
|
||||
fn prove_med(vegger: &[(u64, &str, f64)]) -> String {
|
||||
let mut s = String::from(
|
||||
"ISO-10303-21;\nHEADER;ENDSEC;\nDATA;\n #1= IFCSIUNIT(*,.LENGTHUNIT.,.MILLI.,.METRE.);\n #2= IFCUNITASSIGNMENT((#1));\n #3= IFCPROJECT('p',$,'Prosjekt',$,$,$,$,(#40),#2);\n #4= IFCDIRECTION((0.,1.));\n #40= IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-5,#50,#4);\n #5= IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);\n #10= IFCBUILDING('b',$,'Testhus',$,$,#20,$,$,.ELEMENT.,$,$,$);\n #20= IFCLOCALPLACEMENT($,#21);\n #21= IFCAXIS2PLACEMENT3D(#22,$,$);\n #22= IFCCARTESIANPOINT((0.,0.,0.));\n #200= IFCPROPERTYSINGLEVALUE('ThermalTransmittance',$,IFCREAL(0.18),$);\n #201= IFCPROPERTYSINGLEVALUE('IsExternal',$,IFCBOOLEAN(.T.),$);\n #202= IFCPROPERTYSET('ps',$,'Pset_WallCommon',$,(#200,#201));\n #300= IFCSPACE('s',$,'Stue',$,$,#20,$,'Stue',.ELEMENT.,.INTERNAL.,$);\n #310= IFCQUANTITYAREA('NetFloorArea',$,$,100.0,$);\n #311= IFCQUANTITYVOLUME('NetVolume',$,$,250.0,$);\n #312= IFCELEMENTQUANTITY('q2',$,'BaseQuantities',$,$,(#310,#311));\n #313= IFCRELDEFINESBYPROPERTIES('r3',$,$,$,(#300),#312);\n",
|
||||
);
|
||||
@@ -1727,8 +1953,9 @@ mod tester {
|
||||
p + 8
|
||||
));
|
||||
};
|
||||
vegg(100, "Sorvegg", -5.0);
|
||||
vegg(120, "Nordvegg", 5.0);
|
||||
for (nr, navn, y) in vegger {
|
||||
vegg(*nr, navn, *y);
|
||||
}
|
||||
s.push_str("ENDSEC;\nEND-ISO-10303-21;");
|
||||
s
|
||||
}
|
||||
@@ -1774,6 +2001,24 @@ mod tester {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn samme_vegg_i_flere_etasjer_telles_en_gang() {
|
||||
// Den samme veggen på nøyaktig samme sted to ganger, slik en eksport gjør når den
|
||||
// legger de samme delene i flere etasjer.
|
||||
let tekst = prove_med(&[
|
||||
(100, "Sorvegg", -5.0),
|
||||
(120, "Nordvegg", 5.0),
|
||||
(140, "Sorvegg", -5.0),
|
||||
]);
|
||||
let r = fra_tekst(&tekst, "prove").unwrap();
|
||||
assert_eq!(r.bygning.flate.len(), 2, "flater: {:?}", r.bygning.flate);
|
||||
assert!(
|
||||
r.merknader.iter().any(|m| m.contains("dobbelt i modellen")),
|
||||
"{:?}",
|
||||
r.merknader
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vegger_faar_himmelretning_i_navnet() {
|
||||
let r = fra_tekst(&prove(), "prove").unwrap();
|
||||
@@ -1921,6 +2166,20 @@ mod tester {
|
||||
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]
|
||||
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.
|
||||
|
||||
+50
-1
@@ -27,6 +27,11 @@ pub struct Hovedflate {
|
||||
pub normal: [f64; 3],
|
||||
/// Utstrekning i flatens plan: (u_min, u_maks, v_min, v_maks), m.
|
||||
plan: [f64; 4],
|
||||
/// Midlere høyde over delens eget nullpunkt, m. Sier hvilken etasje delen ligger i når
|
||||
/// plasseringen i filen ikke gjør det.
|
||||
pub hoyde: f64,
|
||||
/// Øverste punkt over delens eget nullpunkt, m. For en kjellervegg er det overkant mur.
|
||||
pub topp: f64,
|
||||
}
|
||||
|
||||
impl Nett {
|
||||
@@ -95,10 +100,23 @@ impl Nett {
|
||||
let (normal, sum) = grupper
|
||||
.into_iter()
|
||||
.max_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal))?;
|
||||
let hoyde = if self.punkter.is_empty() {
|
||||
0.0
|
||||
} else {
|
||||
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 {
|
||||
areal: sum / 2.0,
|
||||
normal,
|
||||
plan: self.utstrekning(normal, skala),
|
||||
hoyde,
|
||||
topp: if topp.is_finite() { topp } else { 0.0 },
|
||||
})
|
||||
}
|
||||
|
||||
@@ -173,9 +191,21 @@ pub fn samlet_hovedflate(deler: &[Nett], skala: f64) -> Option<Hovedflate> {
|
||||
grupper.push(f);
|
||||
}
|
||||
}
|
||||
let areal = grupper.iter().map(|g| g.areal).sum();
|
||||
let areal: f64 = grupper.iter().map(|g| g.areal).sum();
|
||||
// Arealvektet høyde, slik at et sammensatt dekke havner der mesteparten av det ligger.
|
||||
let hoyde = if areal > 0.0 {
|
||||
grupper.iter().map(|g| g.hoyde * g.areal).sum::<f64>() / areal
|
||||
} else {
|
||||
grupper[0].hoyde
|
||||
};
|
||||
let topp = grupper
|
||||
.iter()
|
||||
.map(|g| g.topp)
|
||||
.fold(f64::NEG_INFINITY, f64::max);
|
||||
Some(Hovedflate {
|
||||
areal,
|
||||
hoyde,
|
||||
topp,
|
||||
..grupper[0]
|
||||
})
|
||||
}
|
||||
@@ -194,6 +224,22 @@ fn overlapper(a: &[f64; 4], b: &[f64; 4]) -> bool {
|
||||
dekning(a[0], a[1], b[0], b[1]) && dekning(a[2], a[3], b[2], b[3])
|
||||
}
|
||||
|
||||
impl Hovedflate {
|
||||
/// Omkretsen av flatens omriss, m, regnet av utstrekningen i flatens eget plan.
|
||||
///
|
||||
/// Et gulv mot grunnen trenger omkretsen for B′ etter NS-EN ISO 13370. Omrisset regnes
|
||||
/// som et rektangel, som stemmer godt for de fleste grunnflater.
|
||||
pub fn omkrets(&self) -> f64 {
|
||||
let bredde = self.plan[1] - self.plan[0];
|
||||
let lengde = self.plan[3] - self.plan[2];
|
||||
if bredde > 0.0 && lengde > 0.0 {
|
||||
2.0 * (bredde + lengde)
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Henter geometrien til et produkt via `Representation`, med én oppføring per geometridel.
|
||||
pub fn netter_for_produkt(fil: &Fil, representasjon: Option<u64>) -> Vec<Nett> {
|
||||
let mut deler = Vec::new();
|
||||
@@ -546,6 +592,9 @@ mod tester {
|
||||
let h = n.hovedflate(1.0).unwrap();
|
||||
assert!((h.areal - 12.0).abs() < 1e-9, "areal ble {}", h.areal);
|
||||
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.
|
||||
|
||||
@@ -215,6 +215,7 @@ pub fn fra_tekst(tekst: &str, standardnavn: &str) -> Result<Importrapport> {
|
||||
lufting: Lufting::default(),
|
||||
drift: Drift::default(),
|
||||
scenario: Vec::new(),
|
||||
rapport: Rapport::default(),
|
||||
};
|
||||
Ok(Importrapport { bygning, merknader })
|
||||
}
|
||||
|
||||
+14
-3
@@ -173,18 +173,29 @@ pub fn kjor_med_klima(
|
||||
.map(|s| s.andel_tappevannstap)
|
||||
.sum::<f64>()
|
||||
.min(1.0);
|
||||
// Uten gjenvinnbart systemtap blir netto energibehov uavhengig av varmeanlegget, slik
|
||||
// energirammene i TEK er regnet. Med, slik ISO 52000-1 og NS 3031:2025 beskriver det,
|
||||
// varmer tapet rommene det står i.
|
||||
let med = if lost.systemtap_som_tilskudd {
|
||||
1.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let tilskudd = Systemtilskudd {
|
||||
tappevann: tappevannstap * systemer_oppsett.tappevann_tap_i_sone * (1.0 - sone_tappevann),
|
||||
tappevann: med
|
||||
* tappevannstap
|
||||
* systemer_oppsett.tappevann_tap_i_sone
|
||||
* (1.0 - sone_tappevann),
|
||||
sone_fast: lost
|
||||
.soner
|
||||
.iter()
|
||||
.map(|s| s.fast_effekt + tappevannstap * s.andel_tappevannstap)
|
||||
.map(|s| s.fast_effekt + med * tappevannstap * s.andel_tappevannstap)
|
||||
.collect(),
|
||||
sone_andel_romvarme: lost
|
||||
.soner
|
||||
.iter()
|
||||
.map(|s| {
|
||||
s.andel_distribusjonstap * systemer_oppsett.distribusjon_tap_ut
|
||||
med * s.andel_distribusjonstap * systemer_oppsett.distribusjon_tap_ut
|
||||
/ (1.0 - systemer_oppsett.distribusjon_tap_ut).max(1e-6)
|
||||
})
|
||||
.collect(),
|
||||
|
||||
@@ -114,6 +114,8 @@ pub struct Lost {
|
||||
pub lufting: Option<(f64, f64)>,
|
||||
pub regulering: Regulering,
|
||||
pub innvendig_varmekapasitet: f64,
|
||||
/// Teller systemtapet fra tappevann og varmedistribusjon som varmetilskudd i sonene?
|
||||
pub systemtap_som_tilskudd: bool,
|
||||
pub maks_varmeeffekt: Option<f64>,
|
||||
pub maks_kjoleeffekt: Option<f64>,
|
||||
pub albedo: f64,
|
||||
@@ -334,6 +336,7 @@ impl Lost {
|
||||
};
|
||||
|
||||
let innvendig_varmekapasitet = b.drift.innvendig_varmekapasitet.unwrap_or(10_000.0);
|
||||
let systemtap_som_tilskudd = b.drift.systemtap_som_tilskudd.unwrap_or(true);
|
||||
|
||||
// Vinduslufting brukes når bygningen ikke har kjøleanlegg.
|
||||
let lufting = if b.lufting.ingen
|
||||
@@ -402,6 +405,7 @@ impl Lost {
|
||||
lufting,
|
||||
regulering: Regulering::Operativ,
|
||||
innvendig_varmekapasitet,
|
||||
systemtap_som_tilskudd,
|
||||
maks_varmeeffekt: b.oppvarming.maks_effekt,
|
||||
maks_kjoleeffekt: b.kjoling.maks_effekt,
|
||||
albedo: b.klima.albedo.unwrap_or(0.2),
|
||||
|
||||
@@ -82,6 +82,26 @@ pub struct Bygning {
|
||||
pub drift: Drift,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub scenario: Vec<Scenario>,
|
||||
#[serde(default)]
|
||||
pub rapport: Rapport,
|
||||
}
|
||||
|
||||
/// Utseendet på rapporten, slik at den kan følge firmaets øvrige dokumenter.
|
||||
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Rapport {
|
||||
/// Logo på forsiden. Filen kopieres til utdatakatalogen ved siden av rapporten.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub logo: Option<PathBuf>,
|
||||
/// Aksentfarge, for eksempel «#880000». Utelatt: enoks egen.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub farge: Option<String>,
|
||||
/// Avsenderlinje nederst på hver side, for eksempel firma og organisasjonsnummer.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub avsender: Option<String>,
|
||||
/// Skrift for overskrifter. Utelatt: enoks egen.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub skrift_overskrift: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
@@ -648,6 +668,13 @@ pub struct Drift {
|
||||
/// Effektiv innvendig varmekapasitet for luft og inventar, J/(m²K). Utelatt: 10 000 (ISO 52016-1).
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub innvendig_varmekapasitet: Option<f64>,
|
||||
/// Skal varmetapet fra tappevanns- og varmeanlegget telle som varmetilskudd i rommene?
|
||||
///
|
||||
/// Utelatt: ja, slik ISO 52000-1 og NS 3031:2025 beskriver gjenvinnbart systemtap. Sett
|
||||
/// den til `false` for et netto energibehov som er uavhengig av varmeanlegget, slik
|
||||
/// energirammene i TEK er regnet.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub systemtap_som_tilskudd: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
|
||||
+332
-17
@@ -2,6 +2,7 @@
|
||||
|
||||
use crate::kjor::Resultat;
|
||||
use crate::merking::skala::BOKSTAVER;
|
||||
use crate::modell::lost::Forutsetning;
|
||||
use crate::modell::{FlateType, Mot};
|
||||
use crate::rapport::svg;
|
||||
use crate::Result;
|
||||
@@ -66,6 +67,12 @@ fn rad(celler: &[String]) -> String {
|
||||
pub fn skriv(r: &Resultat, katalog: &Path, scenarier: &[(String, Resultat)]) -> Result<()> {
|
||||
std::fs::create_dir_all(katalog)?;
|
||||
std::fs::write(katalog.join("stil.typ"), STIL)?;
|
||||
// Logoen følger med, slik at rapporten kan kompileres for seg selv senere. Mangler
|
||||
// filen, lages rapporten uten den.
|
||||
if let (Some(logo), Some(navn)) = (&r.bygning.rapport.logo, logo_navn(r)) {
|
||||
std::fs::copy(logo, katalog.join(navn))
|
||||
.with_context(|| format!("kopierer logoen {}", logo.display()))?;
|
||||
}
|
||||
std::fs::write(katalog.join("fig-maaned.svg"), svg::maanedsdiagram(r))?;
|
||||
std::fs::write(katalog.join("fig-varighet.svg"), svg::varighetskurve(r))?;
|
||||
std::fs::write(katalog.join("fig-karakter.svg"), svg::karakterstige(r))?;
|
||||
@@ -100,15 +107,32 @@ fn kilde(r: &Resultat, scenarier: &[(String, Resultat)]) -> String {
|
||||
let mut s = String::new();
|
||||
|
||||
s.push_str("#import \"stil.typ\": *\n\n");
|
||||
s.push_str(&format!(
|
||||
"#show: oppsett.with(tittel: \"{} — energirapport\", bunntekst: \"{}\")\n\n",
|
||||
e(&l.navn),
|
||||
e(&bunntekst(r))
|
||||
));
|
||||
let rap = &r.bygning.rapport;
|
||||
let mut valg = format!(
|
||||
"tittel: \"{} — energirapport\", bunntekst: \"{}\"",
|
||||
streng(&l.navn),
|
||||
streng(&bunntekst(r))
|
||||
);
|
||||
if let Some(a) = &rap.avsender {
|
||||
valg.push_str(&format!(", avsender: \"{}\"", streng(a)));
|
||||
}
|
||||
if let Some(f) = &rap.farge {
|
||||
valg.push_str(&format!(", farge: rgb(\"{}\")", streng(f)));
|
||||
}
|
||||
if let Some(sk) = &rap.skrift_overskrift {
|
||||
valg.push_str(&format!(", skrift: \"{}\"", streng(sk)));
|
||||
}
|
||||
s.push_str(&format!("#show: oppsett.with({valg})\n\n"));
|
||||
|
||||
// ── forside ─────────────────────────────────────────────────────
|
||||
if let Some(navn) = logo_navn(r) {
|
||||
s.push_str(&format!(
|
||||
"#align(right, image(\"{}\", height: 1.7cm))\n\n#v(-0.9cm)\n",
|
||||
streng(&navn)
|
||||
));
|
||||
}
|
||||
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",
|
||||
e(&l.navn)
|
||||
));
|
||||
@@ -414,7 +438,10 @@ fn kilde(r: &Resultat, scenarier: &[(String, Resultat)]) -> String {
|
||||
|
||||
// ── inndata ─────────────────────────────────────────────────────
|
||||
s.push_str("#pagebreak()\n\n= Inndata\n\n== Bygningskropp\n\n");
|
||||
s.push_str("#tabell(\n kolonner: (1fr, auto, auto, auto, auto, auto),\n justering: (left, left, right, right, right, left),\n overskrift: (\"Bygningsdel\", \"Type\", \"Areal, m²\", \"U-verdi\", \"Orientering\", \"Mot\"),\n rader: (\n");
|
||||
s.push_str("#tabell(\n kolonner: (1fr, auto, auto, auto, auto, auto, auto),\n justering: (left, left, right, right, right, right, left),\n overskrift: (\"Bygningsdel\", \"Type\", \"Antall\", \"Areal, m²\", \"U-verdi\", \"Orientering\", \"Mot\"),\n rader: (\n");
|
||||
// Like bygningsdeler står på én linje: modellen deler gjerne den samme konstruksjonen i
|
||||
// mange like vinduer eller veggfelt, og da sier antallet mer enn én rad per stykk.
|
||||
let mut samlet: Vec<(String, String, String, String, usize, f64)> = Vec::new();
|
||||
for f in &l.flater {
|
||||
let typ = if f.glass {
|
||||
"Vindu/dør"
|
||||
@@ -430,15 +457,62 @@ fn kilde(r: &Resultat, scenarier: &[(String, Resultat)]) -> String {
|
||||
Mot::Grunn => "grunnen",
|
||||
Mot::Uoppvarmet => "uoppvarmet",
|
||||
};
|
||||
let navn = f
|
||||
.navn
|
||||
.split(" #")
|
||||
.next()
|
||||
.unwrap_or(&f.navn)
|
||||
.trim()
|
||||
.to_string();
|
||||
let nokkel = (
|
||||
navn,
|
||||
typ.to_string(),
|
||||
tall(f.u_verdi, 2),
|
||||
himmelretning(f.orientering, f.helning),
|
||||
mot.to_string(),
|
||||
);
|
||||
match samlet.iter_mut().find(|(n, t, u, o, _, _)| {
|
||||
(n.as_str(), t.as_str(), u.as_str(), o.as_str())
|
||||
== (
|
||||
nokkel.0.as_str(),
|
||||
nokkel.1.as_str(),
|
||||
nokkel.2.as_str(),
|
||||
nokkel.3.as_str(),
|
||||
)
|
||||
}) {
|
||||
Some((_, _, _, _, antall, areal)) => {
|
||||
*antall += 1;
|
||||
*areal += f.areal;
|
||||
}
|
||||
None => samlet.push((nokkel.0, nokkel.1, nokkel.2, nokkel.3, 1, f.areal)),
|
||||
}
|
||||
let _ = nokkel.4;
|
||||
}
|
||||
for (navn, typ, u, orientering, antall, areal) in &samlet {
|
||||
let mot = l
|
||||
.flater
|
||||
.iter()
|
||||
.find(|f| f.navn.split(" #").next().unwrap_or(&f.navn).trim() == navn)
|
||||
.map(|f| match f.mot {
|
||||
Mot::Ute => "det fri",
|
||||
Mot::Grunn => "grunnen",
|
||||
Mot::Uoppvarmet => "uoppvarmet",
|
||||
})
|
||||
.unwrap_or("det fri");
|
||||
s.push_str(&format!(
|
||||
" {}\n",
|
||||
rad(&[
|
||||
e(&f.navn),
|
||||
typ.into(),
|
||||
tall(f.areal, 1),
|
||||
tall(f.u_verdi, 2),
|
||||
himmelretning(f.orientering, f.helning),
|
||||
mot.into()
|
||||
e(navn),
|
||||
typ.clone(),
|
||||
if *antall > 1 {
|
||||
antall.to_string()
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
tall(*areal, 1),
|
||||
u.clone(),
|
||||
orientering.clone(),
|
||||
mot.into(),
|
||||
])
|
||||
));
|
||||
}
|
||||
@@ -473,14 +547,28 @@ fn kilde(r: &Resultat, scenarier: &[(String, Resultat)]) -> String {
|
||||
if l.forutsetninger.is_empty() {
|
||||
s.push_str("Alle inndata er oppgitt i bygningsbeskrivelsen.\n\n");
|
||||
} else {
|
||||
s.push_str("#tabell(\n kolonner: (auto, auto, 1fr),\n justering: (left, left, left),\n overskrift: (\"Felt\", \"Verdi\", \"Kilde\"),\n rader: (\n");
|
||||
for f in &l.forutsetninger {
|
||||
s.push_str("#tabell(\n kolonner: (1.25fr, 1fr, 1.15fr, auto, auto, auto, auto),\n justering: (left, left, left, center, center, center, left),\n overskrift: (\"Felt\", \"Verdi\", \"Gjelder\", \"Normert\", \"Byggeår\", \"Antatt\", \"Standard\"),\n rader: (\n");
|
||||
for f in grupper_forutsetninger(&l.forutsetninger) {
|
||||
s.push_str(&format!(
|
||||
" {}\n",
|
||||
rad(&[e(&f.felt), e(&f.verdi), e(&f.kilde)])
|
||||
rad(&[
|
||||
e(&f.felt),
|
||||
e(&f.verdi),
|
||||
e(&f.gjelder),
|
||||
kryss(f.normert),
|
||||
kryss(f.byggeaar),
|
||||
kryss(f.antatt),
|
||||
e(&f.standard),
|
||||
])
|
||||
));
|
||||
}
|
||||
s.push_str(" ),\n)\n\n");
|
||||
s.push_str(
|
||||
"#merknad[*Normert* er standardverdier for bygningskategorien, som skal brukes i \
|
||||
energimerket. *Byggeår* er typiske verdier for byggeperioden, brukt der modellen ikke \
|
||||
oppgir noe. *Antatt* er satt av beregningen og bør kontrolleres. *Standard* viser \
|
||||
hvilken standard verdien er regnet etter.]\n\n",
|
||||
);
|
||||
}
|
||||
for m in &r.merknader {
|
||||
s.push_str(&format!("#merknad[{}]\n\n", e(m)));
|
||||
@@ -530,14 +618,204 @@ fn kilde(r: &Resultat, scenarier: &[(String, Resultat)]) -> String {
|
||||
}
|
||||
|
||||
s.push_str(&format!(
|
||||
"\n#v(1em)\n#text(size: 8pt, fill: mid)[Beregnet med enok {} etter NS 3031:2025 (timesmetode, \
|
||||
"\n#v(1em)\n#text(size: 8pt, fill: mid)[Beregnet {} med enok {} etter NS 3031:2025 (timesmetode, \
|
||||
NS-EN ISO 52016-1). Karakterskala fastsatt av Enova SF, gjeldende fra 1. januar 2026. \
|
||||
Klimadata: TMY for norske kommuner basert på CERRA 1991–2020 (CC-BY-4.0).]\n",
|
||||
i_dag(),
|
||||
env!("CARGO_PKG_VERSION")
|
||||
));
|
||||
s
|
||||
}
|
||||
|
||||
/// Kilden uten tall, slik at «… av konstruksjonens 3,39 W/(m²K) og 1,29 m dybde» og den
|
||||
/// samme setningen for en annen vegg regnes som samme slags kilde.
|
||||
fn kildenokkel(kilde: &str) -> String {
|
||||
let mut ut = String::with_capacity(kilde.len());
|
||||
let mut i_tall = false;
|
||||
for c in kilde.chars() {
|
||||
if c.is_ascii_digit() {
|
||||
if !i_tall {
|
||||
ut.push('#');
|
||||
}
|
||||
i_tall = true;
|
||||
} else {
|
||||
i_tall = false;
|
||||
ut.push(c);
|
||||
}
|
||||
}
|
||||
ut
|
||||
}
|
||||
|
||||
/// Tallet fremst i en verdi som «1.353 W/(m²K)», når verdien begynner med et tall.
|
||||
fn tallverdi(verdi: &str) -> Option<f64> {
|
||||
let tall: String = verdi
|
||||
.chars()
|
||||
.take_while(|c| c.is_ascii_digit() || *c == '.' || *c == ',' || *c == '-')
|
||||
.collect();
|
||||
tall.replace(',', ".").parse().ok()
|
||||
}
|
||||
|
||||
/// Slår to verdier med samme enhet sammen til et intervall, «1,21–1,39 W/(m²K)».
|
||||
fn slaa_sammen_intervall(a: &str, b: &str) -> String {
|
||||
let enhet = a.trim_start_matches(|c: char| {
|
||||
c.is_ascii_digit() || c == '.' || c == ',' || c == '-' || c == '–'
|
||||
});
|
||||
let mut tall: Vec<f64> = Vec::new();
|
||||
for del in a.split('–').chain(b.split('–')) {
|
||||
if let Some(t) = tallverdi(del.trim()) {
|
||||
tall.push(t);
|
||||
}
|
||||
}
|
||||
let minste = tall.iter().copied().fold(f64::INFINITY, f64::min);
|
||||
let storste = tall.iter().copied().fold(f64::NEG_INFINITY, f64::max);
|
||||
if (storste - minste).abs() < 5e-3 {
|
||||
format!("{minste:.2}{enhet}")
|
||||
} else {
|
||||
format!("{minste:.2}–{storste:.2}{enhet}")
|
||||
}
|
||||
}
|
||||
|
||||
/// Norsk desimaltegn i tall, uten å røre tekst som «ISO 52016-1».
|
||||
fn komma(verdi: &str) -> String {
|
||||
let mut ut = String::with_capacity(verdi.len());
|
||||
let tegn: Vec<char> = verdi.chars().collect();
|
||||
for (i, c) in tegn.iter().enumerate() {
|
||||
let mellom_tall = i > 0
|
||||
&& i + 1 < tegn.len()
|
||||
&& tegn[i - 1].is_ascii_digit()
|
||||
&& tegn[i + 1].is_ascii_digit();
|
||||
ut.push(if *c == '.' && mellom_tall { ',' } else { *c });
|
||||
}
|
||||
ut
|
||||
}
|
||||
|
||||
fn kryss(på: bool) -> String {
|
||||
if på {
|
||||
"✓".into()
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// En rad i forutsetningstabellen: like forutsetninger for mange bygningsdeler slås sammen.
|
||||
struct Forutsetningsrad {
|
||||
felt: String,
|
||||
verdi: String,
|
||||
gjelder: String,
|
||||
normert: bool,
|
||||
byggeaar: bool,
|
||||
antatt: bool,
|
||||
standard: String,
|
||||
}
|
||||
|
||||
/// Slår sammen forutsetninger som gjelder flere bygningsdeler, og deler kilden i kolonner.
|
||||
///
|
||||
/// «flate.Yttervegg nord.u_verdi» og «flate.Yttervegg sør.u_verdi» med samme verdi og kilde
|
||||
/// blir til én rad «flate.u_verdi» som gjelder 2 flater.
|
||||
fn grupper_forutsetninger(forutsetninger: &[Forutsetning]) -> Vec<Forutsetningsrad> {
|
||||
let flertall = |prefiks: &str, antall: usize| match prefiks {
|
||||
"flate" => format!("{antall} flater"),
|
||||
"vindu" => format!("{antall} vinduer"),
|
||||
"sone" => format!("{antall} soner"),
|
||||
annet => format!("{antall} × {annet}"),
|
||||
};
|
||||
let mut rader: Vec<(String, String, String, Vec<String>, String)> = Vec::new();
|
||||
for f in forutsetninger {
|
||||
// «prefiks.navn.felt» deles i mønsteret «prefiks.felt» og navnet det gjelder.
|
||||
let (prefiks, resten) = f.felt.split_once('.').unwrap_or(("", f.felt.as_str()));
|
||||
let (navn, felt) = match resten.rsplit_once('.') {
|
||||
Some((navn, felt)) => (navn.to_string(), felt.to_string()),
|
||||
None => (String::new(), resten.to_string()),
|
||||
};
|
||||
let monster = if prefiks.is_empty() {
|
||||
felt.clone()
|
||||
} else {
|
||||
format!("{prefiks}.{felt}")
|
||||
};
|
||||
match rader
|
||||
.iter_mut()
|
||||
.find(|(m, v, k, _, _)| *m == monster && *v == f.verdi && *k == f.kilde)
|
||||
{
|
||||
Some((_, _, _, navn_liste, _)) => navn_liste.push(navn),
|
||||
None => rader.push((
|
||||
monster,
|
||||
f.verdi.clone(),
|
||||
f.kilde.clone(),
|
||||
vec![navn],
|
||||
prefiks.to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
// Verdier som bare skiller seg litt, som en U-verdi mot grunnen regnet for hver vegg,
|
||||
// samles til ett intervall i stedet for én rad per vegg.
|
||||
let mut samlet: Vec<(String, String, String, Vec<String>, String)> = Vec::new();
|
||||
for (felt, verdi, kilde, navn, prefiks) in rader {
|
||||
// Kilden bærer ofte tallene for den enkelte veggen; de maskeres, slik at samme
|
||||
// slags kilde kjennes igjen.
|
||||
let like = samlet
|
||||
.iter_mut()
|
||||
.find(|(f, _, k, _, _)| *f == felt && kildenokkel(k) == kildenokkel(&kilde));
|
||||
match like {
|
||||
Some((_, v, _, n, _)) if tallverdi(v).is_some() && tallverdi(&verdi).is_some() => {
|
||||
*v = slaa_sammen_intervall(v, &verdi);
|
||||
n.extend(navn);
|
||||
}
|
||||
_ => samlet.push((felt, verdi, kilde, navn, prefiks)),
|
||||
}
|
||||
}
|
||||
samlet
|
||||
.into_iter()
|
||||
.map(|(felt, verdi, kilde, navn, prefiks)| {
|
||||
let gjelder = match navn.len() {
|
||||
0 => String::new(),
|
||||
1 if navn[0].is_empty() => String::new(),
|
||||
1 => navn[0].clone(),
|
||||
n => flertall(&prefiks, n),
|
||||
};
|
||||
let lav = kilde.to_lowercase();
|
||||
let standard = [
|
||||
"NS-EN ISO 13370",
|
||||
"NS-EN ISO 13789",
|
||||
"ISO 52016-1",
|
||||
"NS 3031",
|
||||
]
|
||||
.into_iter()
|
||||
.find(|s| kilde.contains(s))
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
let normert = lav.contains("normert");
|
||||
let byggeaar = lav.contains("typisk for");
|
||||
Forutsetningsrad {
|
||||
felt,
|
||||
verdi: komma(&verdi),
|
||||
gjelder,
|
||||
normert,
|
||||
byggeaar,
|
||||
antatt: !normert && !byggeaar && standard.is_empty() || lav.contains("antatt"),
|
||||
standard,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Tekst i en Typst-strengliteral: bare omvendt skråstrek og anførselstegn skal ut.
|
||||
fn streng(s: &str) -> String {
|
||||
s.replace('\\', "\\\\").replace('"', "\\\"")
|
||||
}
|
||||
|
||||
/// Filnavnet på logoen, når den er oppgitt og finnes.
|
||||
fn logo_navn(r: &Resultat) -> Option<String> {
|
||||
let logo = r.bygning.rapport.logo.as_ref()?;
|
||||
if !logo.exists() {
|
||||
eprintln!(
|
||||
" ! rapport.logo: fant ikke {}; rapporten lages uten logo.",
|
||||
logo.display()
|
||||
);
|
||||
return None;
|
||||
}
|
||||
Some(logo.file_name()?.to_str()?.to_string())
|
||||
}
|
||||
|
||||
fn bunntekst(r: &Resultat) -> String {
|
||||
let b = &r.bygning.bygning;
|
||||
let mut deler = Vec::new();
|
||||
@@ -550,9 +828,34 @@ fn bunntekst(r: &Resultat) -> String {
|
||||
if let Some(e) = &b.energiraadgiver {
|
||||
deler.push(e.clone());
|
||||
}
|
||||
deler.push(format!("enok {} · {}", env!("CARGO_PKG_VERSION"), i_dag()));
|
||||
deler.join(" · ")
|
||||
}
|
||||
|
||||
/// Dagens dato som ISO-8601 i UTC.
|
||||
fn i_dag() -> String {
|
||||
let sekunder = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|d| d.as_secs() as i64)
|
||||
.unwrap_or(0);
|
||||
let (aar, maaned, dag) = dato_av_dagnummer(sekunder.div_euclid(86_400));
|
||||
format!("{aar:04}-{maaned:02}-{dag:02}")
|
||||
}
|
||||
|
||||
/// Kalenderdato av antall døgn siden 1970-01-01, etter Howard Hinnants algoritme.
|
||||
fn dato_av_dagnummer(dager: i64) -> (i64, i64, i64) {
|
||||
let z = dager + 719_468;
|
||||
let era = z.div_euclid(146_097);
|
||||
let doe = z.rem_euclid(146_097);
|
||||
let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
|
||||
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
|
||||
let mp = (5 * doy + 2) / 153;
|
||||
let dag = doy - (153 * mp + 2) / 5 + 1;
|
||||
let maaned = if mp < 10 { mp + 3 } else { mp - 9 };
|
||||
let aar = yoe + era * 400 + i64::from(maaned <= 2);
|
||||
(aar, maaned, dag)
|
||||
}
|
||||
|
||||
fn himmelretning(orientering: f64, helning: f64) -> String {
|
||||
if helning < 20.0 {
|
||||
return "horisontal".into();
|
||||
@@ -566,3 +869,15 @@ fn himmelretning(orientering: f64, helning: f64) -> String {
|
||||
let i = (((orientering.rem_euclid(360.0)) + 22.5) / 45.0).floor() as usize % 8;
|
||||
retninger[i].into()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tester {
|
||||
use super::dato_av_dagnummer;
|
||||
|
||||
#[test]
|
||||
fn dagnummer_gir_riktig_dato() {
|
||||
assert_eq!(dato_av_dagnummer(0), (1970, 1, 1));
|
||||
assert_eq!(dato_av_dagnummer(19_723), (2024, 1, 1));
|
||||
assert_eq!(dato_av_dagnummer(20_351), (2025, 9, 20));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
//! Kontroll mot de publiserte referansebyggene for energirammene i TEK17.
|
||||
//!
|
||||
//! Multiconsult for DiBK, «Grunnlag og forutsetninger for beregnede energirammer»
|
||||
//! (128298-RIEn-NOT-001, 13. november 2015), gir geometri (tabell 1), komponentverdier
|
||||
//! (tabell 2) og beregnet netto energibehov (tabell 3) for tretten bygningskategorier,
|
||||
//! beregnet med Simien etter NS 3031:2014. Modellene er gjengitt i `eksempler/referansebygg/`.
|
||||
//!
|
||||
//! Dette er den eneste kontrollen der hele beregningen kan sammenlignes med et publisert
|
||||
//! resultat for flere bygningskategorier. Testen låser dagens avvik, slik at det blir synlig
|
||||
//! både når motoren nærmer seg de publiserte tallene og når noe gjør avviket større.
|
||||
//!
|
||||
//! Kjør `cargo test --release referansebygg_2015 -- --nocapture` for hele tabellen.
|
||||
|
||||
use enok::data::Datakilde;
|
||||
use enok::{inndata, kjor};
|
||||
use std::path::Path;
|
||||
|
||||
/// Fil, publisert netto energibehov, og enoks verdi slik den er i dag, kWh/(m²·år).
|
||||
const BYGG: [(&str, f64, f64); 7] = [
|
||||
("smaahus", 107.5, 87.7),
|
||||
("boligblokk", 91.8, 79.7),
|
||||
("barnehage", 132.0, 81.1),
|
||||
("kontorbygg", 115.3, 93.9),
|
||||
("skolebygg", 106.8, 73.6),
|
||||
("idrettsbygg", 144.3, 111.1),
|
||||
("kulturbygg", 128.0, 74.2),
|
||||
];
|
||||
|
||||
fn netto(navn: &str) -> f64 {
|
||||
let sti = format!("eksempler/referansebygg/{navn}.toml");
|
||||
let b = inndata::les_bygning(Path::new(&sti)).unwrap();
|
||||
let r = kjor::kjor(&b, &Datakilde::default()).unwrap();
|
||||
r.budsjett.netto_spesifikk(r.lost.bra)
|
||||
}
|
||||
|
||||
/// Verdiene skal ikke endre seg uten at noen har ment det.
|
||||
#[test]
|
||||
fn referansebyggene_gir_de_samme_tallene() {
|
||||
println!(
|
||||
"\n {:14}{:>10}{:>12}{:>9}",
|
||||
"bygg", "enok", "publisert", "avvik"
|
||||
);
|
||||
let mut avvik = Vec::new();
|
||||
for (navn, publisert, laast) in BYGG {
|
||||
let n = netto(navn);
|
||||
println!(
|
||||
" {navn:14}{n:10.1}{publisert:12.1}{:8.1} %",
|
||||
100.0 * (n - publisert) / publisert
|
||||
);
|
||||
avvik.push(100.0 * (n - publisert) / publisert);
|
||||
assert!(
|
||||
(n - laast).abs() < 1.0,
|
||||
"{navn}: {n:.1} kWh/(m²·år), låst verdi {laast:.1}"
|
||||
);
|
||||
}
|
||||
let snitt = avvik.iter().sum::<f64>() / avvik.len() as f64;
|
||||
println!(" {:14}{:>10}{:>12}{snitt:8.1} %\n", "snitt", "", "");
|
||||
}
|
||||
|
||||
/// Varmetapstallet for småhuset skal treffe den publiserte verdien for TEK17-nivå.
|
||||
///
|
||||
/// Geometri og U-verdier er dermed riktig gjengitt, og avviket i netto energibehov ligger i
|
||||
/// den dynamiske beregningen, ikke i inndataene.
|
||||
#[test]
|
||||
fn varmetapstallet_for_smaahuset_stemmer() {
|
||||
let b = inndata::les_bygning(Path::new("eksempler/referansebygg/smaahus.toml")).unwrap();
|
||||
let r = kjor::kjor(&b, &Datakilde::default()).unwrap();
|
||||
let publisert = 0.69;
|
||||
assert!(
|
||||
(r.varmetap.totalt - publisert).abs() < 0.03,
|
||||
"varmetapstall {:.2}, publisert {publisert:.2}",
|
||||
r.varmetap.totalt
|
||||
);
|
||||
}
|
||||
|
||||
/// De faste postene følger av normerte verdier og skal stemme uavhengig av motoren.
|
||||
#[test]
|
||||
fn faste_poster_i_referansebyggene() {
|
||||
let post = |r: &kjor::Resultat, nr: &str| {
|
||||
r.budsjett
|
||||
.poster
|
||||
.iter()
|
||||
.find(|p| p.nr == nr)
|
||||
.map(|p| p.netto / r.lost.bra)
|
||||
.unwrap_or(0.0)
|
||||
};
|
||||
let b = inndata::les_bygning(Path::new("eksempler/referansebygg/smaahus.toml")).unwrap();
|
||||
let r = kjor::kjor(&b, &Datakilde::default()).unwrap();
|
||||
assert!((post(&r, "2") - 25.1).abs() < 0.2, "varmtvann");
|
||||
assert!((post(&r, "5") - 11.4).abs() < 0.2, "belysning");
|
||||
assert!((post(&r, "6") - 17.5).abs() < 0.2, "teknisk utstyr");
|
||||
}
|
||||
+45
-8
@@ -7,11 +7,32 @@
|
||||
#let lys = rgb("#f4f1ee")
|
||||
#let kantlinje = rgb("#ddd4cc")
|
||||
|
||||
// Aksentfarge og overskriftsskrift kan settes av rapporten, slik at den kan følge
|
||||
// firmaets øvrige dokumenter. Verdiene under er enoks egne.
|
||||
#let aksent-valgt = state("aksent-valgt", aksent)
|
||||
#let skrift-valgt = state("skrift-valgt", none)
|
||||
|
||||
#let font-heading = ("Libertinus Serif", "DejaVu Serif", "Noto Serif")
|
||||
#let font-body = ("Libertinus Serif", "DejaVu Serif", "Noto Serif")
|
||||
#let font-tall = ("DejaVu Sans Mono", "Liberation Mono")
|
||||
|
||||
#let oppsett(tittel: "", undertittel: "", bunntekst: "", doc) = {
|
||||
/// Skriften rapporten har bedt om, ellers enoks egen.
|
||||
#let overskriftsskrift() = {
|
||||
let valgt = skrift-valgt.get()
|
||||
if valgt == none { font-heading } else { (valgt,) + font-heading }
|
||||
}
|
||||
|
||||
#let oppsett(
|
||||
tittel: "",
|
||||
undertittel: "",
|
||||
bunntekst: "",
|
||||
avsender: "",
|
||||
farge: none,
|
||||
skrift: none,
|
||||
doc,
|
||||
) = {
|
||||
aksent-valgt.update(if farge == none { aksent } else { farge })
|
||||
skrift-valgt.update(skrift)
|
||||
set page(
|
||||
paper: "a4",
|
||||
margin: (top: 2.4cm, bottom: 2.4cm, left: 2.2cm, right: 2.2cm),
|
||||
@@ -20,32 +41,36 @@
|
||||
grid(
|
||||
columns: (1fr, auto),
|
||||
align: (left + horizon, right + horizon),
|
||||
text(font: font-heading, size: 8pt, fill: mid)[#tittel],
|
||||
text(font: overskriftsskrift(), size: 8pt, fill: mid)[#tittel],
|
||||
text(font: font-body, size: 8pt, fill: mid)[#counter(page).display()],
|
||||
)
|
||||
line(length: 100%, stroke: 0.4pt + kantlinje)
|
||||
}
|
||||
},
|
||||
footer: context {
|
||||
if counter(page).get().first() > 1 and bunntekst != "" {
|
||||
if counter(page).get().first() > 1 and (bunntekst != "" or avsender != "") {
|
||||
line(length: 100%, stroke: 0.4pt + kantlinje)
|
||||
v(3pt)
|
||||
if avsender != "" {
|
||||
text(font: font-body, size: 7.5pt, fill: mid)[#avsender]
|
||||
linebreak()
|
||||
}
|
||||
text(font: font-body, size: 7.5pt, fill: mid)[#bunntekst]
|
||||
}
|
||||
},
|
||||
)
|
||||
set text(font: font-body, size: 10pt, fill: moerk, lang: "nb")
|
||||
set par(justify: true, leading: 0.62em)
|
||||
show heading: it => {
|
||||
set text(font: font-heading, fill: aksent)
|
||||
show heading: it => context {
|
||||
set text(font: overskriftsskrift(), fill: aksent-valgt.get())
|
||||
block(above: 1.2em, below: 0.6em, it)
|
||||
}
|
||||
show heading.where(level: 1): it => {
|
||||
show heading.where(level: 1): it => context {
|
||||
set text(size: 15pt, weight: "bold")
|
||||
block(above: 1.6em, below: 0.7em)[
|
||||
#it.body
|
||||
#v(-0.45em)
|
||||
#line(length: 100%, stroke: 1pt + aksent)
|
||||
#line(length: 100%, stroke: 1pt + aksent-valgt.get())
|
||||
]
|
||||
}
|
||||
show heading.where(level: 2): set text(size: 11.5pt, weight: "bold")
|
||||
@@ -55,7 +80,8 @@
|
||||
}
|
||||
|
||||
// Tabell med stripete rader og linje under overskriften.
|
||||
#let tabell(kolonner: (), overskrift: (), rader: (), justering: none, sum-rader: ()) = {
|
||||
#let tabell(kolonner: (), overskrift: (), rader: (), justering: none, sum-rader: ()) = context {
|
||||
let aksent = aksent-valgt.get()
|
||||
let j = if justering == none { kolonner.map(_ => right) } else { justering }
|
||||
block(breakable: true, table(
|
||||
columns: kolonner,
|
||||
@@ -71,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.
|
||||
#let karakterrute(bokstav, verdi, farge) = {
|
||||
box(
|
||||
@@ -99,3 +135,4 @@
|
||||
stroke: (left: 2pt + aksent-lys),
|
||||
text(size: 9pt)[#innhold],
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user