diff --git a/BuildResidentialHPXML/measure.xml b/BuildResidentialHPXML/measure.xml
index 265fdb1e9c..e412739e6e 100644
--- a/BuildResidentialHPXML/measure.xml
+++ b/BuildResidentialHPXML/measure.xml
@@ -3,8 +3,8 @@
3.0
build_residential_hpxml
a13a8983-2b01-4930-8af2-42030b6e4233
- 235c0e9c-de56-46b1-8712-83df120d39b8
- 20230501T221652Z
+ 7e14b99a-8028-4045-a389-bc04982c8bdf
+ 20230503T221424Z
2C38F48B
BuildResidentialHPXML
HPXML Builder
@@ -6583,18 +6583,18 @@
-
- geometry.rb
- rb
- resource
- D9E75C2E
-
build_residential_hpxml_test.rb
rb
test
7F6A3D85
+
+ geometry.rb
+ rb
+ resource
+ 5184329E
+
OpenStudio
diff --git a/BuildResidentialHPXML/resources/geometry.rb b/BuildResidentialHPXML/resources/geometry.rb
index e9b35673ba..b10393ee8d 100644
--- a/BuildResidentialHPXML/resources/geometry.rb
+++ b/BuildResidentialHPXML/resources/geometry.rb
@@ -19,7 +19,7 @@ def self.get_abs_azimuth(relative_azimuth, building_orientation)
def self.get_absolute_tilt(tilt_str, roof_pitch, epw_file)
tilt_str = tilt_str.downcase
if tilt_str.start_with? 'roofpitch'
- roof_angle = Math.atan(roof_pitch / 12.0) * 180.0 / Math::PI
+ roof_angle = UnitConversions.convert(Math.atan(roof_pitch / 12.0), 'rad', 'deg')
return Float(eval(tilt_str.gsub('roofpitch', roof_angle.to_s)))
elsif tilt_str.start_with? 'latitude'
return Float(eval(tilt_str.gsub('latitude', epw_file.latitude.to_s)))
diff --git a/HPXMLtoOpenStudio/measure.rb b/HPXMLtoOpenStudio/measure.rb
index 0309ca0f55..7b50d53615 100644
--- a/HPXMLtoOpenStudio/measure.rb
+++ b/HPXMLtoOpenStudio/measure.rb
@@ -399,72 +399,80 @@ def self.add_roofs(runner, model, spaces)
next if surfaces.empty?
# Apply construction
- has_radiant_barrier = roof.radiant_barrier
- if has_radiant_barrier
- radiant_barrier_grade = roof.radiant_barrier_grade
- end
- # FUTURE: Create Constructions.get_air_film(surface) method; use in measure.rb and hpxml_translator_test.rb
- inside_film = Material.AirFilmRoof(Geometry.get_roof_pitch([surfaces[0]]))
- outside_film = Material.AirFilmOutside
- mat_roofing = Material.RoofMaterial(roof.roof_type)
- if @apply_ashrae140_assumptions
- inside_film = Material.AirFilmRoofASHRAE140
- outside_film = Material.AirFilmOutsideASHRAE140
- end
- mat_int_finish = Material.InteriorFinishMaterial(roof.interior_finish_type, roof.interior_finish_thickness)
- if mat_int_finish.nil?
- fallback_mat_int_finish = nil
- else
- fallback_mat_int_finish = Material.InteriorFinishMaterial(mat_int_finish.name, 0.1) # Try thin material
- end
- install_grade = 1
- assembly_r = roof.insulation_assembly_r_value
+ inside_film = roof.additional_properties.inside_film
+ outside_film = roof.additional_properties.outside_film
- if not mat_int_finish.nil?
- # Closed cavity
- constr_sets = [
- WoodStudConstructionSet.new(Material.Stud2x(8.0), 0.07, 20.0, 0.75, mat_int_finish, mat_roofing), # 2x8, 24" o.c. + R20
- WoodStudConstructionSet.new(Material.Stud2x(8.0), 0.07, 10.0, 0.75, mat_int_finish, mat_roofing), # 2x8, 24" o.c. + R10
- WoodStudConstructionSet.new(Material.Stud2x(8.0), 0.07, 0.0, 0.75, mat_int_finish, mat_roofing), # 2x8, 24" o.c.
- WoodStudConstructionSet.new(Material.Stud2x6, 0.07, 0.0, 0.75, mat_int_finish, mat_roofing), # 2x6, 24" o.c.
- WoodStudConstructionSet.new(Material.Stud2x4, 0.07, 0.0, 0.5, mat_int_finish, mat_roofing), # 2x4, 16" o.c.
- WoodStudConstructionSet.new(Material.Stud2x4, 0.01, 0.0, 0.0, fallback_mat_int_finish, mat_roofing), # Fallback
- ]
- match, constr_set, cavity_r = Constructions.pick_wood_stud_construction_set(assembly_r, constr_sets, inside_film, outside_film)
+ if roof.has_detailed_construction
+ # Layer-by-layer construction
+
+ Constructions.apply_detailed_construction(model, surfaces, roof.detailed_construction, inside_film,
+ outside_film, roof.solar_absorptance, roof.emittance)
- Constructions.apply_closed_cavity_roof(model, surfaces, "#{roof.id} construction",
- cavity_r, install_grade,
- constr_set.stud.thick_in,
- true, constr_set.framing_factor,
- constr_set.mat_int_finish,
- constr_set.osb_thick_in, constr_set.rigid_r,
- constr_set.mat_ext_finish, has_radiant_barrier,
- inside_film, outside_film, radiant_barrier_grade,
- roof.solar_absorptance, roof.emittance)
else
- # Open cavity
- constr_sets = [
- GenericConstructionSet.new(10.0, 0.5, nil, mat_roofing), # w/R-10 rigid
- GenericConstructionSet.new(0.0, 0.5, nil, mat_roofing), # Standard
- GenericConstructionSet.new(0.0, 0.0, nil, mat_roofing), # Fallback
- ]
- match, constr_set, layer_r = Constructions.pick_generic_construction_set(assembly_r, constr_sets, inside_film, outside_film)
+ # Assembly R-value
- cavity_r = 0
- cavity_ins_thick_in = 0
- framing_factor = 0
- framing_thick_in = 0
+ has_radiant_barrier = roof.radiant_barrier
+ if has_radiant_barrier
+ radiant_barrier_grade = roof.radiant_barrier_grade
+ end
+ # FUTURE: Create Constructions.get_air_film(surface) method; use in measure.rb and hpxml_translator_test.rb
+ mat_roofing = Material.RoofMaterial(roof.roof_type)
+ mat_int_finish = Material.InteriorFinishMaterial(roof.interior_finish_type, roof.interior_finish_thickness)
+ if mat_int_finish.nil?
+ fallback_mat_int_finish = nil
+ else
+ fallback_mat_int_finish = Material.InteriorFinishMaterial(mat_int_finish.name, 0.1) # Try thin material
+ end
- Constructions.apply_open_cavity_roof(model, surfaces, "#{roof.id} construction",
- cavity_r, install_grade, cavity_ins_thick_in,
- framing_factor, framing_thick_in,
- constr_set.osb_thick_in, layer_r + constr_set.rigid_r,
- constr_set.mat_ext_finish, has_radiant_barrier,
- inside_film, outside_film, radiant_barrier_grade,
- roof.solar_absorptance, roof.emittance)
+ install_grade = 1
+ assembly_r = roof.insulation_assembly_r_value
+
+ if not mat_int_finish.nil?
+ # Closed cavity
+ constr_sets = [
+ WoodStudConstructionSet.new(Material.Stud2x(8.0), 0.07, 20.0, 0.75, mat_int_finish, mat_roofing), # 2x8, 24" o.c. + R20
+ WoodStudConstructionSet.new(Material.Stud2x(8.0), 0.07, 10.0, 0.75, mat_int_finish, mat_roofing), # 2x8, 24" o.c. + R10
+ WoodStudConstructionSet.new(Material.Stud2x(8.0), 0.07, 0.0, 0.75, mat_int_finish, mat_roofing), # 2x8, 24" o.c.
+ WoodStudConstructionSet.new(Material.Stud2x6, 0.07, 0.0, 0.75, mat_int_finish, mat_roofing), # 2x6, 24" o.c.
+ WoodStudConstructionSet.new(Material.Stud2x4, 0.07, 0.0, 0.5, mat_int_finish, mat_roofing), # 2x4, 16" o.c.
+ WoodStudConstructionSet.new(Material.Stud2x4, 0.01, 0.0, 0.0, fallback_mat_int_finish, mat_roofing), # Fallback
+ ]
+ match, constr_set, cavity_r = Constructions.pick_wood_stud_construction_set(assembly_r, constr_sets, inside_film, outside_film)
+
+ Constructions.apply_closed_cavity_roof(model, surfaces, "#{roof.id} construction",
+ cavity_r, install_grade,
+ constr_set.stud.thick_in,
+ true, constr_set.framing_factor,
+ constr_set.mat_int_finish,
+ constr_set.osb_thick_in, constr_set.rigid_r,
+ constr_set.mat_ext_finish, has_radiant_barrier,
+ inside_film, outside_film, radiant_barrier_grade,
+ roof.solar_absorptance, roof.emittance)
+ else
+ # Open cavity
+ constr_sets = [
+ GenericConstructionSet.new(10.0, 0.5, nil, mat_roofing), # w/R-10 rigid
+ GenericConstructionSet.new(0.0, 0.5, nil, mat_roofing), # Standard
+ GenericConstructionSet.new(0.0, 0.0, nil, mat_roofing), # Fallback
+ ]
+ match, constr_set, layer_r = Constructions.pick_generic_construction_set(assembly_r, constr_sets, inside_film, outside_film)
+
+ cavity_r = 0
+ cavity_ins_thick_in = 0
+ framing_factor = 0
+ framing_thick_in = 0
+
+ Constructions.apply_open_cavity_roof(model, surfaces, "#{roof.id} construction",
+ cavity_r, install_grade, cavity_ins_thick_in,
+ framing_factor, framing_thick_in,
+ constr_set.osb_thick_in, layer_r + constr_set.rigid_r,
+ constr_set.mat_ext_finish, has_radiant_barrier,
+ inside_film, outside_film, radiant_barrier_grade,
+ roof.solar_absorptance, roof.emittance)
+ end
+ Constructions.check_surface_assembly_rvalue(runner, surfaces, inside_film, outside_film, assembly_r, match)
end
- Constructions.check_surface_assembly_rvalue(runner, surfaces, inside_film, outside_film, assembly_r, match)
end
end
@@ -513,26 +521,31 @@ def self.add_walls(runner, model, spaces)
next if surfaces.empty?
# Apply construction
- # The code below constructs a reasonable wall construction based on the
- # wall type while ensuring the correct assembly R-value.
- inside_film = Material.AirFilmVertical
- if wall.is_exterior
- outside_film = Material.AirFilmOutside
- mat_ext_finish = Material.ExteriorFinishMaterial(wall.siding)
+ inside_film = wall.additional_properties.inside_film
+ outside_film = wall.additional_properties.outside_film
+
+ if wall.has_detailed_construction
+ # Layer-by-layer construction
+
+ Constructions.apply_detailed_construction(model, surfaces, wall.detailed_construction, inside_film,
+ outside_film, wall.solar_absorptance, wall.emittance)
else
- outside_film = Material.AirFilmVertical
- mat_ext_finish = nil
- end
- if @apply_ashrae140_assumptions
- inside_film = Material.AirFilmVerticalASHRAE140
- outside_film = Material.AirFilmOutsideASHRAE140
- end
- mat_int_finish = Material.InteriorFinishMaterial(wall.interior_finish_type, wall.interior_finish_thickness)
+ # Assembly R-value
+ # The code below constructs a reasonable wall construction based on the
+ # wall type while ensuring the correct assembly R-value.
- Constructions.apply_wall_construction(runner, model, surfaces, wall.id, wall.wall_type, wall.insulation_assembly_r_value,
- mat_int_finish, inside_film, outside_film, mat_ext_finish, wall.solar_absorptance,
- wall.emittance)
+ if wall.is_exterior
+ mat_ext_finish = Material.ExteriorFinishMaterial(wall.siding)
+ else
+ mat_ext_finish = nil
+ end
+ mat_int_finish = Material.InteriorFinishMaterial(wall.interior_finish_type, wall.interior_finish_thickness)
+
+ Constructions.apply_wall_construction(runner, model, surfaces, wall.id, wall.wall_type, wall.insulation_assembly_r_value,
+ mat_int_finish, inside_film, outside_film, mat_ext_finish, wall.solar_absorptance,
+ wall.emittance)
+ end
end
end
@@ -578,33 +591,42 @@ def self.add_rim_joists(runner, model, spaces)
# Apply construction
- inside_film = Material.AirFilmVertical
- if rim_joist.is_exterior
- outside_film = Material.AirFilmOutside
- mat_ext_finish = Material.ExteriorFinishMaterial(rim_joist.siding)
+ inside_film = rim_joist.additional_properties.inside_film
+ outside_film = rim_joist.additional_properties.outside_film
+
+ if rim_joist.has_detailed_construction
+ # Layer-by-layer construction
+
+ Constructions.apply_detailed_construction(model, surfaces, rim_joist.detailed_construction, inside_film,
+ outside_film, rim_joist.solar_absorptance, rim_joist.emittance)
else
- outside_film = Material.AirFilmVertical
- mat_ext_finish = nil
- end
+ # Assembly R-value
+
+ if rim_joist.is_exterior
+ mat_ext_finish = Material.ExteriorFinishMaterial(rim_joist.siding)
+ else
+ mat_ext_finish = nil
+ end
- assembly_r = rim_joist.insulation_assembly_r_value
-
- constr_sets = [
- WoodStudConstructionSet.new(Material.Stud2x(2.0), 0.17, 20.0, 2.0, nil, mat_ext_finish), # 2x4 + R20
- WoodStudConstructionSet.new(Material.Stud2x(2.0), 0.17, 10.0, 2.0, nil, mat_ext_finish), # 2x4 + R10
- WoodStudConstructionSet.new(Material.Stud2x(2.0), 0.17, 0.0, 2.0, nil, mat_ext_finish), # 2x4
- WoodStudConstructionSet.new(Material.Stud2x(2.0), 0.01, 0.0, 0.0, nil, mat_ext_finish), # Fallback
- ]
- match, constr_set, cavity_r = Constructions.pick_wood_stud_construction_set(assembly_r, constr_sets, inside_film, outside_film)
- install_grade = 1
-
- Constructions.apply_rim_joist(model, surfaces, "#{rim_joist.id} construction",
- cavity_r, install_grade, constr_set.framing_factor,
- constr_set.mat_int_finish, constr_set.osb_thick_in,
- constr_set.rigid_r, constr_set.mat_ext_finish,
- inside_film, outside_film, rim_joist.solar_absorptance,
- rim_joist.emittance)
- Constructions.check_surface_assembly_rvalue(runner, surfaces, inside_film, outside_film, assembly_r, match)
+ assembly_r = rim_joist.insulation_assembly_r_value
+
+ constr_sets = [
+ WoodStudConstructionSet.new(Material.Stud2x(2.0), 0.17, 20.0, 2.0, nil, mat_ext_finish), # 2x4 + R20
+ WoodStudConstructionSet.new(Material.Stud2x(2.0), 0.17, 10.0, 2.0, nil, mat_ext_finish), # 2x4 + R10
+ WoodStudConstructionSet.new(Material.Stud2x(2.0), 0.17, 0.0, 2.0, nil, mat_ext_finish), # 2x4
+ WoodStudConstructionSet.new(Material.Stud2x(2.0), 0.01, 0.0, 0.0, nil, mat_ext_finish), # Fallback
+ ]
+ match, constr_set, cavity_r = Constructions.pick_wood_stud_construction_set(assembly_r, constr_sets, inside_film, outside_film)
+ install_grade = 1
+
+ Constructions.apply_rim_joist(model, surfaces, "#{rim_joist.id} construction",
+ cavity_r, install_grade, constr_set.framing_factor,
+ constr_set.mat_int_finish, constr_set.osb_thick_in,
+ constr_set.rigid_r, constr_set.mat_ext_finish,
+ inside_film, outside_film, rim_joist.solar_absorptance,
+ rim_joist.emittance)
+ Constructions.check_surface_assembly_rvalue(runner, surfaces, inside_film, outside_film, assembly_r, match)
+ end
end
end
diff --git a/HPXMLtoOpenStudio/measure.xml b/HPXMLtoOpenStudio/measure.xml
index c7da85e0c0..09e9e57754 100644
--- a/HPXMLtoOpenStudio/measure.xml
+++ b/HPXMLtoOpenStudio/measure.xml
@@ -3,8 +3,8 @@
3.0
hpxm_lto_openstudio
b1543b30-9465-45ff-ba04-1d1f85e763bc
- 4816c10b-5a41-436e-a355-cfd8edd5b044
- 20230502T201104Z
+ 9ceb2d4d-cab8-437a-88c0-99e65f1bbff0
+ 20230503T221427Z
D8922A73
HPXMLtoOpenStudio
HPXML to OpenStudio Translator
@@ -259,12 +259,6 @@
test
03FE784E
-
- materials.rb
- rb
- resource
- 24DCB986
-
test_simcontrols.rb
rb
@@ -391,12 +385,6 @@
resource
9E02B1C4
-
- constructions.rb
- rb
- resource
- F6E89CBA
-
geometry.rb
rb
@@ -485,7 +473,25 @@
hpxml_schema/HPXML.xsd
xsd
resource
- 75097E83
+ CA905BB5
+
+
+ materials.rb
+ rb
+ resource
+ 49C655B4
+
+
+ constructions.rb
+ rb
+ resource
+ 0C36E521
+
+
+ xmlvalidator.rb
+ rb
+ resource
+ CFCD83CD
test_schedules.rb
@@ -515,13 +521,7 @@
hvac_sizing.rb
rb
resource
- 1AD76DC6
-
-
- xmlvalidator.rb
- rb
- resource
- CFCD83CD
+ 218EA20F
test_miscloads.rb
@@ -541,29 +541,23 @@
resource
EA5F3E1B
+
+ hpxml_defaults.rb
+ rb
+ resource
+ 811F4F08
+
hpxml.rb
rb
resource
- 6E7B4D41
+ 3104449F
hpxml_schematron/EPvalidator.xml
xml
resource
- 63A11C83
-
-
- test_validation.rb
- rb
- test
- F0B3DCEF
-
-
- test_defaults.rb
- rb
- test
- F1FEF43F
+ 69B938B4
@@ -574,13 +568,19 @@
measure.rb
rb
script
- 1E7FB5AF
+ BD97DFD4
- hpxml_defaults.rb
+ test_validation.rb
rb
- resource
- A466A879
+ test
+ 8D65BD7F
+
+
+ test_defaults.rb
+ rb
+ test
+ F1FEF43F
diff --git a/HPXMLtoOpenStudio/resources/constructions.rb b/HPXMLtoOpenStudio/resources/constructions.rb
index f7b2259c0a..033a424e28 100644
--- a/HPXMLtoOpenStudio/resources/constructions.rb
+++ b/HPXMLtoOpenStudio/resources/constructions.rb
@@ -3,6 +3,107 @@
class Constructions
# Container class for walls, floors/ceilings, roofs, etc.
+ # Generic layer-by-layer construction from an HPXML DetailedConstruction element
+ def self.apply_detailed_construction(model, surfaces, detailed_construction,
+ inside_film, outside_film, solar_absorptance, emittance)
+
+ return if surfaces.empty?
+
+ # Define construction
+ constr = create_from_detailed_construction(detailed_construction, inside_film, outside_film)
+
+ constr.set_exterior_material_properties(solar_absorptance, emittance)
+ constr.set_interior_material_properties()
+
+ # Create and assign construction to surfaces
+ constr.create_and_assign_constructions(surfaces, model)
+ end
+
+ def self.create_from_detailed_construction(detailed_construction, inside_film, outside_film)
+ # FIXME: Need to re-normalize area_fractions to sum to 1
+
+ # Define materials
+ mats = {}
+ detailed_construction.construction_layers.each_with_index do |layer, i|
+ layer.layer_materials.each_with_index do |material, j|
+ if material.material_type.nil?
+ material_name = "#{detailed_construction.id} - Layer #{i + 1}, Material #{j + 1}"
+ else
+ material_name = "#{detailed_construction.id} - #{material.material_type}"
+ end
+ if not material.conductivity.nil?
+ k_in = UnitConversions.convert(material.conductivity, 'ft', 'in')
+ elsif not layer.layer_thickness.nil?
+ k_in = layer.layer_thickness / material.r_value
+ end
+ if not k_in.nil?
+ mat = Material.new(name: material_name, thick_in: layer.layer_thickness, k_in: k_in, rho: material.density, cp: material.specific_heat)
+ else
+ mat = NoMassMaterial.new(name: material_name, rvalue: material.r_value)
+ end
+ mats[[i, j]] = mat
+ end
+ end
+
+ # Determine unique paths
+ cumulative_area_fracs = []
+ detailed_construction.construction_layers.each do |layer|
+ layer_area_fracs = []
+ frac = 0.0
+ layer.layer_materials.each do |material|
+ frac += material.area_fraction
+ layer_area_fracs << frac
+ end
+ cumulative_area_fracs << layer_area_fracs
+ end
+ cumulative_area_fracs = cumulative_area_fracs.flatten.uniq.sort
+ path_fracs = []
+ cumulative_area_fracs.each_with_index do |area_frac, i|
+ if i == 0
+ path_fracs << area_frac
+ else
+ path_fracs << area_frac - cumulative_area_fracs[0..i - 1].sum
+ end
+ end
+
+ # Define construction
+ constr = Construction.new(detailed_construction.id, path_fracs)
+ constr.add_layer(outside_film)
+ detailed_construction.construction_layers.each_with_index do |layer, i|
+ if layer.layer_type.nil?
+ material_names = layer.layer_materials.map { |m| m.material_type }
+ if material_names.any? { |m| m.nil? }
+ layer_name = "#{detailed_construction.id} - Layer #{i + 1}"
+ else
+ layer_name = "#{detailed_construction.id} - #{material_names.join('|')}"
+ end
+ else
+ layer_name = "#{detailed_construction.id} - #{layer.layer_type}"
+ end
+ if layer.layer_materials.size == 1
+ # Single material for entire layer
+ constr.add_layer(mats[[i, 0]], layer_name)
+ else
+ # Create array of materials that align with path_fracs
+ layer_mats = []
+ path_fracs.each do |area_frac|
+ frac = 0.0
+ mat = nil
+ layer.layer_materials.each_with_index do |material, j|
+ frac += material.area_fraction
+ mat = mats[[i, j]]
+ break if frac >= area_frac
+ end
+ layer_mats << mat
+ end
+ constr.add_layer(layer_mats, layer_name)
+ end
+ end
+ constr.add_layer(inside_film)
+
+ return constr
+ end
+
def self.apply_wood_stud_wall(model, surfaces, constr_name,
cavity_r, install_grade, cavity_depth_in, cavity_filled,
framing_factor, mat_int_finish, osb_thick_in,
@@ -2465,8 +2566,8 @@ def validate
# Check for valid object types
@layers_materials.each do |layer_materials|
layer_materials.each do |mat|
- if (not mat.is_a? Material)
- fail 'Invalid construction: Materials must be instances of Material classes.'
+ if (not mat.is_a? Material) && (not mat.is_a? NoMassMaterial)
+ fail 'Invalid construction: Materials must be instances of Material or NoMassMaterial classes.'
end
end
end
@@ -2540,27 +2641,44 @@ def self.create_os_material(model, material)
mat.setSolarHeatGainCoefficient(material.shgc)
else
# Material already exists?
- model.getStandardOpaqueMaterials.each do |mat|
- next if !mat.name.to_s.start_with?(material.name)
- next if mat.roughness.downcase.to_s != 'rough'
- next if (mat.thickness - UnitConversions.convert(material.thick_in, 'in', 'm')).abs > tolerance
- next if (mat.conductivity - UnitConversions.convert(material.k, 'Btu/(hr*ft*R)', 'W/(m*K)')).abs > tolerance
- next if (mat.density - UnitConversions.convert(material.rho, 'lbm/ft^3', 'kg/m^3')).abs > tolerance
- next if (mat.specificHeat - UnitConversions.convert(material.cp, 'Btu/(lbm*R)', 'J/(kg*K)')).abs > tolerance
- next if (mat.thermalAbsorptance - material.tAbs.to_f).abs > tolerance
- next if (mat.solarAbsorptance - material.sAbs.to_f).abs > tolerance
-
- return mat
+ if material.is_a? Material
+ model.getStandardOpaqueMaterials.each do |mat|
+ next if !mat.name.to_s.start_with?(material.name)
+ next if mat.roughness.downcase.to_s != 'rough'
+ next if (mat.thickness - UnitConversions.convert(material.thick_in, 'in', 'm')).abs > tolerance
+ next if (mat.conductivity - UnitConversions.convert(material.k, 'Btu/(hr*ft*R)', 'W/(m*K)')).abs > tolerance
+ next if (mat.density - UnitConversions.convert(material.rho, 'lbm/ft^3', 'kg/m^3')).abs > tolerance
+ next if (mat.specificHeat - UnitConversions.convert(material.cp, 'Btu/(lbm*R)', 'J/(kg*K)')).abs > tolerance
+ next if (mat.thermalAbsorptance - material.tAbs.to_f).abs > tolerance
+ next if (mat.solarAbsorptance - material.sAbs.to_f).abs > tolerance
+
+ return mat
+ end
+ elsif material.is_a? NoMassMaterial
+ model.getMasslessOpaqueMaterials.each do |mat|
+ next if !mat.name.to_s.start_with?(material.name)
+ next if mat.roughness.downcase.to_s != 'rough'
+ next if (mat.thermalResistance - UnitConversions.convert(material.rvalue, 'hr*ft^2*F/Btu', 'm^2*K/W')).abs > tolerance
+ next if (mat.thermalAbsorptance - material.tAbs.to_f).abs > tolerance
+ next if (mat.solarAbsorptance - material.sAbs.to_f).abs > tolerance
+
+ return mat
+ end
end
# New material
- mat = OpenStudio::Model::StandardOpaqueMaterial.new(model)
+ if material.is_a? Material
+ mat = OpenStudio::Model::StandardOpaqueMaterial.new(model)
+ mat.setThickness(UnitConversions.convert(material.thick_in, 'in', 'm'))
+ mat.setConductivity(UnitConversions.convert(material.k, 'Btu/(hr*ft*R)', 'W/(m*K)'))
+ mat.setDensity(UnitConversions.convert(material.rho, 'lbm/ft^3', 'kg/m^3'))
+ mat.setSpecificHeat(UnitConversions.convert(material.cp, 'Btu/(lbm*R)', 'J/(kg*K)'))
+ elsif material.is_a? NoMassMaterial
+ mat = OpenStudio::Model::MasslessOpaqueMaterial.new(model)
+ mat.setThermalResistance(UnitConversions.convert(material.rvalue, 'hr*ft^2*F/Btu', 'm^2*K/W'))
+ end
mat.setName(name)
mat.setRoughness('Rough')
- mat.setThickness(UnitConversions.convert(material.thick_in, 'in', 'm'))
- mat.setConductivity(UnitConversions.convert(material.k, 'Btu/(hr*ft*R)', 'W/(m*K)'))
- mat.setDensity(UnitConversions.convert(material.rho, 'lbm/ft^3', 'kg/m^3'))
- mat.setSpecificHeat(UnitConversions.convert(material.cp, 'Btu/(lbm*R)', 'J/(kg*K)'))
if not material.tAbs.nil?
mat.setThermalAbsorptance(material.tAbs)
end
diff --git a/HPXMLtoOpenStudio/resources/hpxml.rb b/HPXMLtoOpenStudio/resources/hpxml.rb
index 42a88ce38b..7196cd276d 100644
--- a/HPXMLtoOpenStudio/resources/hpxml.rb
+++ b/HPXMLtoOpenStudio/resources/hpxml.rb
@@ -2192,6 +2192,10 @@ def from_oga(hpxml)
end
class Roof < BaseElement
+ def initialize(hpxml_object, *args)
+ @detailed_construction = DetailedConstruction.new(hpxml_object)
+ super(hpxml_object, *args)
+ end
ATTRS = [:id, :interior_adjacent_to, :area, :azimuth, :orientation, :roof_type,
:roof_color, :solar_absorptance, :emittance, :pitch, :radiant_barrier,
:insulation_id, :insulation_assembly_r_value, :insulation_cavity_r_value,
@@ -2199,6 +2203,7 @@ class Roof < BaseElement
:interior_finish_type, :interior_finish_thickness, :framing_factor,
:framing_size, :framing_spacing]
attr_accessor(*ATTRS)
+ attr_reader(:detailed_construction)
def skylights
return @hpxml_object.skylights.select { |skylight| skylight.roof_idref == @id }
@@ -2241,6 +2246,10 @@ def is_conditioned
return HPXML::is_conditioned(self)
end
+ def has_detailed_construction
+ return !@detailed_construction.construction_layers.empty?
+ end
+
def delete
@hpxml_object.roofs.delete(self)
skylights.reverse_each do |skylight|
@@ -2254,6 +2263,7 @@ def delete
def check_for_errors
errors = []
begin; net_area; rescue StandardError => e; errors << e.message; end
+ errors += @detailed_construction.check_for_errors
return errors
end
@@ -2286,25 +2296,28 @@ def to_oga(doc)
XMLHelper.add_element(roof, 'Pitch', @pitch, :float) unless @pitch.nil?
XMLHelper.add_element(roof, 'RadiantBarrier', @radiant_barrier, :boolean, @radiant_barrier_isdefaulted) unless @radiant_barrier.nil?
XMLHelper.add_element(roof, 'RadiantBarrierGrade', @radiant_barrier_grade, :integer, @radiant_barrier_grade_isdefaulted) unless @radiant_barrier_grade.nil?
- insulation = XMLHelper.add_element(roof, 'Insulation')
- sys_id = XMLHelper.add_element(insulation, 'SystemIdentifier')
- if not @insulation_id.nil?
- XMLHelper.add_attribute(sys_id, 'id', @insulation_id)
- else
- XMLHelper.add_attribute(sys_id, 'id', @id + 'Insulation')
- end
- XMLHelper.add_element(insulation, 'InsulationGrade', @insulation_grade, :integer) unless @insulation_grade.nil?
- XMLHelper.add_element(insulation, 'AssemblyEffectiveRValue', @insulation_assembly_r_value, :float) unless @insulation_assembly_r_value.nil?
- if not @insulation_cavity_r_value.nil?
- layer = XMLHelper.add_element(insulation, 'Layer')
- XMLHelper.add_element(layer, 'InstallationType', 'cavity', :string)
- XMLHelper.add_element(layer, 'NominalRValue', @insulation_cavity_r_value, :float)
- end
- if not @insulation_continuous_r_value.nil?
- layer = XMLHelper.add_element(insulation, 'Layer')
- XMLHelper.add_element(layer, 'InstallationType', 'continuous', :string)
- XMLHelper.add_element(layer, 'NominalRValue', @insulation_continuous_r_value, :float)
+ if (not @insulation_assembly_r_value.nil?) || (not @insulation_cavity_r_value.nil?) || (not @insulation_continuous_r_value.nil?)
+ insulation = XMLHelper.add_element(roof, 'Insulation')
+ sys_id = XMLHelper.add_element(insulation, 'SystemIdentifier')
+ if not @insulation_id.nil?
+ XMLHelper.add_attribute(sys_id, 'id', @insulation_id)
+ else
+ XMLHelper.add_attribute(sys_id, 'id', @id + 'Insulation')
+ end
+ XMLHelper.add_element(insulation, 'InsulationGrade', @insulation_grade, :integer) unless @insulation_grade.nil?
+ XMLHelper.add_element(insulation, 'AssemblyEffectiveRValue', @insulation_assembly_r_value, :float, @insulation_assembly_r_value_isdefaulted) unless @insulation_assembly_r_value.nil?
+ if not @insulation_cavity_r_value.nil?
+ layer = XMLHelper.add_element(insulation, 'Layer')
+ XMLHelper.add_element(layer, 'InstallationType', 'cavity', :string)
+ XMLHelper.add_element(layer, 'NominalRValue', @insulation_cavity_r_value, :float)
+ end
+ if not @insulation_continuous_r_value.nil?
+ layer = XMLHelper.add_element(insulation, 'Layer')
+ XMLHelper.add_element(layer, 'InstallationType', 'continuous', :string)
+ XMLHelper.add_element(layer, 'NominalRValue', @insulation_continuous_r_value, :float)
+ end
end
+ @detailed_construction.to_oga(roof)
end
def from_oga(roof)
@@ -2338,6 +2351,7 @@ def from_oga(roof)
@insulation_cavity_r_value = XMLHelper.get_value(insulation, "Layer[InstallationType='cavity']/NominalRValue", :float)
@insulation_continuous_r_value = XMLHelper.get_value(insulation, "Layer[InstallationType='continuous']/NominalRValue", :float)
end
+ @detailed_construction.from_oga(XMLHelper.get_element(roof, 'DetailedConstruction'))
end
end
@@ -2356,10 +2370,15 @@ def from_oga(hpxml)
end
class RimJoist < BaseElement
+ def initialize(hpxml_object, *args)
+ @detailed_construction = DetailedConstruction.new(hpxml_object)
+ super(hpxml_object, *args)
+ end
ATTRS = [:id, :exterior_adjacent_to, :interior_adjacent_to, :area, :orientation, :azimuth, :siding,
:color, :solar_absorptance, :emittance, :insulation_id, :insulation_assembly_r_value,
:insulation_cavity_r_value, :insulation_continuous_r_value, :framing_size]
attr_accessor(*ATTRS)
+ attr_reader(:detailed_construction)
def is_exterior
if @exterior_adjacent_to == LocationOutside
@@ -2389,6 +2408,10 @@ def is_conditioned
return HPXML::is_conditioned(self)
end
+ def has_detailed_construction
+ return !@detailed_construction.construction_layers.empty?
+ end
+
def delete
@hpxml_object.rim_joists.delete(self)
@hpxml_object.foundations.each do |foundation|
@@ -2398,6 +2421,7 @@ def delete
def check_for_errors
errors = []
+ errors += @detailed_construction.check_for_errors
return errors
end
@@ -2417,28 +2441,31 @@ def to_oga(doc)
XMLHelper.add_element(rim_joist, 'Color', @color, :string, @color_isdefaulted) unless @color.nil?
XMLHelper.add_element(rim_joist, 'SolarAbsorptance', @solar_absorptance, :float, @solar_absorptance_isdefaulted) unless @solar_absorptance.nil?
XMLHelper.add_element(rim_joist, 'Emittance', @emittance, :float, @emittance_isdefaulted) unless @emittance.nil?
- insulation = XMLHelper.add_element(rim_joist, 'Insulation')
- sys_id = XMLHelper.add_element(insulation, 'SystemIdentifier')
- if not @insulation_id.nil?
- XMLHelper.add_attribute(sys_id, 'id', @insulation_id)
- else
- XMLHelper.add_attribute(sys_id, 'id', @id + 'Insulation')
- end
- XMLHelper.add_element(insulation, 'AssemblyEffectiveRValue', @insulation_assembly_r_value, :float) unless @insulation_assembly_r_value.nil?
- if not @insulation_cavity_r_value.nil?
- layer = XMLHelper.add_element(insulation, 'Layer')
- XMLHelper.add_element(layer, 'InstallationType', 'cavity', :string)
- XMLHelper.add_element(layer, 'NominalRValue', @insulation_cavity_r_value, :float)
- end
- if not @insulation_continuous_r_value.nil?
- layer = XMLHelper.add_element(insulation, 'Layer')
- XMLHelper.add_element(layer, 'InstallationType', 'continuous', :string)
- XMLHelper.add_element(layer, 'NominalRValue', @insulation_continuous_r_value, :float)
+ if (not @insulation_assembly_r_value.nil?) || (not @insulation_cavity_r_value.nil?) || (not @insulation_continuous_r_value.nil?)
+ insulation = XMLHelper.add_element(rim_joist, 'Insulation')
+ sys_id = XMLHelper.add_element(insulation, 'SystemIdentifier')
+ if not @insulation_id.nil?
+ XMLHelper.add_attribute(sys_id, 'id', @insulation_id)
+ else
+ XMLHelper.add_attribute(sys_id, 'id', @id + 'Insulation')
+ end
+ XMLHelper.add_element(insulation, 'AssemblyEffectiveRValue', @insulation_assembly_r_value, :float, @insulation_assembly_r_value_isdefaulted) unless @insulation_assembly_r_value.nil?
+ if not @insulation_cavity_r_value.nil?
+ layer = XMLHelper.add_element(insulation, 'Layer')
+ XMLHelper.add_element(layer, 'InstallationType', 'cavity', :string)
+ XMLHelper.add_element(layer, 'NominalRValue', @insulation_cavity_r_value, :float)
+ end
+ if not @insulation_continuous_r_value.nil?
+ layer = XMLHelper.add_element(insulation, 'Layer')
+ XMLHelper.add_element(layer, 'InstallationType', 'continuous', :string)
+ XMLHelper.add_element(layer, 'NominalRValue', @insulation_continuous_r_value, :float)
+ end
end
if not @framing_size.nil?
floor_joists = XMLHelper.add_element(rim_joist, 'FloorJoists')
XMLHelper.add_element(floor_joists, 'Size', @framing_size, :string) unless @framing_size.nil?
end
+ @detailed_construction.to_oga(rim_joist)
end
def from_oga(rim_joist)
@@ -2462,6 +2489,7 @@ def from_oga(rim_joist)
@insulation_continuous_r_value = XMLHelper.get_value(insulation, "Layer[InstallationType='continuous']/NominalRValue", :float)
end
@framing_size = XMLHelper.get_value(rim_joist, 'FloorJoists/Size', :string)
+ @detailed_construction.from_oga(XMLHelper.get_element(rim_joist, 'DetailedConstruction'))
end
end
@@ -2480,12 +2508,17 @@ def from_oga(hpxml)
end
class Wall < BaseElement
+ def initialize(hpxml_object, *args)
+ @detailed_construction = DetailedConstruction.new(hpxml_object)
+ super(hpxml_object, *args)
+ end
ATTRS = [:id, :exterior_adjacent_to, :interior_adjacent_to, :wall_type, :optimum_value_engineering,
:area, :orientation, :azimuth, :siding, :color, :solar_absorptance, :emittance, :insulation_id,
:insulation_assembly_r_value, :insulation_cavity_r_value, :insulation_continuous_r_value,
:interior_finish_type, :interior_finish_thickness, :attic_wall_type, :framing_factor,
:framing_size, :framing_spacing, :insulation_grade]
attr_accessor(*ATTRS)
+ attr_reader(:detailed_construction)
def windows
return @hpxml_object.windows.select { |window| window.wall_idref == @id }
@@ -2536,6 +2569,10 @@ def is_conditioned
return HPXML::is_conditioned(self)
end
+ def has_detailed_construction
+ return !@detailed_construction.construction_layers.empty?
+ end
+
def delete
@hpxml_object.walls.delete(self)
windows.reverse_each do |window|
@@ -2555,6 +2592,7 @@ def delete
def check_for_errors
errors = []
begin; net_area; rescue StandardError => e; errors << e.message; end
+ errors += @detailed_construction.check_for_errors
return errors
end
@@ -2593,25 +2631,28 @@ def to_oga(doc)
XMLHelper.add_element(interior_finish, 'Type', @interior_finish_type, :string, @interior_finish_type_isdefaulted) unless @interior_finish_type.nil?
XMLHelper.add_element(interior_finish, 'Thickness', @interior_finish_thickness, :float, @interior_finish_thickness_isdefaulted) unless @interior_finish_thickness.nil?
end
- insulation = XMLHelper.add_element(wall, 'Insulation')
- sys_id = XMLHelper.add_element(insulation, 'SystemIdentifier')
- if not @insulation_id.nil?
- XMLHelper.add_attribute(sys_id, 'id', @insulation_id)
- else
- XMLHelper.add_attribute(sys_id, 'id', @id + 'Insulation')
- end
- XMLHelper.add_element(insulation, 'InsulationGrade', @insulation_grade, :integer) unless @insulation_grade.nil?
- XMLHelper.add_element(insulation, 'AssemblyEffectiveRValue', @insulation_assembly_r_value, :float) unless @insulation_assembly_r_value.nil?
- if not @insulation_cavity_r_value.nil?
- layer = XMLHelper.add_element(insulation, 'Layer')
- XMLHelper.add_element(layer, 'InstallationType', 'cavity', :string)
- XMLHelper.add_element(layer, 'NominalRValue', @insulation_cavity_r_value, :float)
- end
- if not @insulation_continuous_r_value.nil?
- layer = XMLHelper.add_element(insulation, 'Layer')
- XMLHelper.add_element(layer, 'InstallationType', 'continuous', :string)
- XMLHelper.add_element(layer, 'NominalRValue', @insulation_continuous_r_value, :float)
+ if (not @insulation_assembly_r_value.nil?) || (not @insulation_cavity_r_value.nil?) || (not @insulation_continuous_r_value.nil?)
+ insulation = XMLHelper.add_element(wall, 'Insulation')
+ sys_id = XMLHelper.add_element(insulation, 'SystemIdentifier')
+ if not @insulation_id.nil?
+ XMLHelper.add_attribute(sys_id, 'id', @insulation_id)
+ else
+ XMLHelper.add_attribute(sys_id, 'id', @id + 'Insulation')
+ end
+ XMLHelper.add_element(insulation, 'InsulationGrade', @insulation_grade, :integer) unless @insulation_grade.nil?
+ XMLHelper.add_element(insulation, 'AssemblyEffectiveRValue', @insulation_assembly_r_value, :float, @insulation_assembly_r_value_isdefaulted) unless @insulation_assembly_r_value.nil?
+ if not @insulation_cavity_r_value.nil?
+ layer = XMLHelper.add_element(insulation, 'Layer')
+ XMLHelper.add_element(layer, 'InstallationType', 'cavity', :string)
+ XMLHelper.add_element(layer, 'NominalRValue', @insulation_cavity_r_value, :float)
+ end
+ if not @insulation_continuous_r_value.nil?
+ layer = XMLHelper.add_element(insulation, 'Layer')
+ XMLHelper.add_element(layer, 'InstallationType', 'continuous', :string)
+ XMLHelper.add_element(layer, 'NominalRValue', @insulation_continuous_r_value, :float)
+ end
end
+ @detailed_construction.to_oga(wall)
end
def from_oga(wall)
@@ -2648,6 +2689,7 @@ def from_oga(wall)
@insulation_cavity_r_value = XMLHelper.get_value(insulation, "Layer[InstallationType='cavity']/NominalRValue", :float)
@insulation_continuous_r_value = XMLHelper.get_value(insulation, "Layer[InstallationType='continuous']/NominalRValue", :float)
end
+ @detailed_construction.from_oga(XMLHelper.get_element(wall, 'DetailedConstruction'))
end
end
@@ -2766,27 +2808,29 @@ def to_oga(doc)
XMLHelper.add_element(interior_finish, 'Type', @interior_finish_type, :string, @interior_finish_type_isdefaulted) unless @interior_finish_type.nil?
XMLHelper.add_element(interior_finish, 'Thickness', @interior_finish_thickness, :float, @interior_finish_thickness_isdefaulted) unless @interior_finish_thickness.nil?
end
- insulation = XMLHelper.add_element(foundation_wall, 'Insulation')
- sys_id = XMLHelper.add_element(insulation, 'SystemIdentifier')
- if not @insulation_id.nil?
- XMLHelper.add_attribute(sys_id, 'id', @insulation_id)
- else
- XMLHelper.add_attribute(sys_id, 'id', @id + 'Insulation')
- end
- XMLHelper.add_element(insulation, 'AssemblyEffectiveRValue', @insulation_assembly_r_value, :float) unless @insulation_assembly_r_value.nil?
- if not @insulation_exterior_r_value.nil?
- layer = XMLHelper.add_element(insulation, 'Layer')
- XMLHelper.add_element(layer, 'InstallationType', 'continuous - exterior', :string)
- XMLHelper.add_element(layer, 'NominalRValue', @insulation_exterior_r_value, :float)
- XMLHelper.add_element(layer, 'DistanceToTopOfInsulation', @insulation_exterior_distance_to_top, :float, @insulation_exterior_distance_to_top_isdefaulted) unless @insulation_exterior_distance_to_top.nil?
- XMLHelper.add_element(layer, 'DistanceToBottomOfInsulation', @insulation_exterior_distance_to_bottom, :float, @insulation_exterior_distance_to_bottom_isdefaulted) unless @insulation_exterior_distance_to_bottom.nil?
- end
- if not @insulation_interior_r_value.nil?
- layer = XMLHelper.add_element(insulation, 'Layer')
- XMLHelper.add_element(layer, 'InstallationType', 'continuous - interior', :string)
- XMLHelper.add_element(layer, 'NominalRValue', @insulation_interior_r_value, :float)
- XMLHelper.add_element(layer, 'DistanceToTopOfInsulation', @insulation_interior_distance_to_top, :float, @insulation_interior_distance_to_top_isdefaulted) unless @insulation_interior_distance_to_top.nil?
- XMLHelper.add_element(layer, 'DistanceToBottomOfInsulation', @insulation_interior_distance_to_bottom, :float, @insulation_interior_distance_to_bottom_isdefaulted) unless @insulation_interior_distance_to_bottom.nil?
+ if (not @insulation_assembly_r_value.nil?) || (not @insulation_exterior_r_value.nil?) || (not @insulation_interior_r_value.nil?)
+ insulation = XMLHelper.add_element(foundation_wall, 'Insulation')
+ sys_id = XMLHelper.add_element(insulation, 'SystemIdentifier')
+ if not @insulation_id.nil?
+ XMLHelper.add_attribute(sys_id, 'id', @insulation_id)
+ else
+ XMLHelper.add_attribute(sys_id, 'id', @id + 'Insulation')
+ end
+ XMLHelper.add_element(insulation, 'AssemblyEffectiveRValue', @insulation_assembly_r_value, :float, @insulation_assembly_r_value_isdefaulted) unless @insulation_assembly_r_value.nil?
+ if not @insulation_exterior_r_value.nil?
+ layer = XMLHelper.add_element(insulation, 'Layer')
+ XMLHelper.add_element(layer, 'InstallationType', 'continuous - exterior', :string)
+ XMLHelper.add_element(layer, 'NominalRValue', @insulation_exterior_r_value, :float)
+ XMLHelper.add_element(layer, 'DistanceToTopOfInsulation', @insulation_exterior_distance_to_top, :float, @insulation_exterior_distance_to_top_isdefaulted) unless @insulation_exterior_distance_to_top.nil?
+ XMLHelper.add_element(layer, 'DistanceToBottomOfInsulation', @insulation_exterior_distance_to_bottom, :float, @insulation_exterior_distance_to_bottom_isdefaulted) unless @insulation_exterior_distance_to_bottom.nil?
+ end
+ if not @insulation_interior_r_value.nil?
+ layer = XMLHelper.add_element(insulation, 'Layer')
+ XMLHelper.add_element(layer, 'InstallationType', 'continuous - interior', :string)
+ XMLHelper.add_element(layer, 'NominalRValue', @insulation_interior_r_value, :float)
+ XMLHelper.add_element(layer, 'DistanceToTopOfInsulation', @insulation_interior_distance_to_top, :float, @insulation_interior_distance_to_top_isdefaulted) unless @insulation_interior_distance_to_top.nil?
+ XMLHelper.add_element(layer, 'DistanceToBottomOfInsulation', @insulation_interior_distance_to_bottom, :float, @insulation_interior_distance_to_bottom_isdefaulted) unless @insulation_interior_distance_to_bottom.nil?
+ end
end
end
@@ -2839,11 +2883,16 @@ def from_oga(hpxml)
end
class Floor < BaseElement
+ def initialize(hpxml_object, *args)
+ @detailed_construction = DetailedConstruction.new(hpxml_object)
+ super(hpxml_object, *args)
+ end
ATTRS = [:id, :exterior_adjacent_to, :interior_adjacent_to, :floor_type, :area, :insulation_id,
:insulation_assembly_r_value, :insulation_cavity_r_value, :insulation_continuous_r_value,
:floor_or_ceiling, :interior_finish_type, :interior_finish_thickness, :insulation_grade,
:framing_factor, :framing_size, :framing_spacing]
attr_accessor(*ATTRS)
+ attr_reader(:detailed_construction)
def is_ceiling
# From the perspective of the living space
@@ -2893,6 +2942,10 @@ def is_conditioned
return HPXML::is_conditioned(self)
end
+ def has_detailed_construction
+ return !@detailed_construction.construction_layers.empty?
+ end
+
def delete
@hpxml_object.floors.delete(self)
@hpxml_object.attics.each do |attic|
@@ -2908,6 +2961,7 @@ def delete
def check_for_errors
errors = []
+ errors += @detailed_construction.check_for_errors
return errors
end
@@ -2937,25 +2991,28 @@ def to_oga(doc)
XMLHelper.add_element(interior_finish, 'Type', @interior_finish_type, :string, @interior_finish_type_isdefaulted) unless @interior_finish_type.nil?
XMLHelper.add_element(interior_finish, 'Thickness', @interior_finish_thickness, :float, @interior_finish_thickness_isdefaulted) unless @interior_finish_thickness.nil?
end
- insulation = XMLHelper.add_element(floor, 'Insulation')
- sys_id = XMLHelper.add_element(insulation, 'SystemIdentifier')
- if not @insulation_id.nil?
- XMLHelper.add_attribute(sys_id, 'id', @insulation_id)
- else
- XMLHelper.add_attribute(sys_id, 'id', @id + 'Insulation')
- end
- XMLHelper.add_element(insulation, 'InsulationGrade', @insulation_grade, :integer) unless @insulation_grade.nil?
- XMLHelper.add_element(insulation, 'AssemblyEffectiveRValue', @insulation_assembly_r_value, :float) unless @insulation_assembly_r_value.nil?
- if not @insulation_cavity_r_value.nil?
- layer = XMLHelper.add_element(insulation, 'Layer')
- XMLHelper.add_element(layer, 'InstallationType', 'cavity', :string)
- XMLHelper.add_element(layer, 'NominalRValue', @insulation_cavity_r_value, :float)
- end
- if not @insulation_continuous_r_value.nil?
- layer = XMLHelper.add_element(insulation, 'Layer')
- XMLHelper.add_element(layer, 'InstallationType', 'continuous', :string)
- XMLHelper.add_element(layer, 'NominalRValue', @insulation_continuous_r_value, :float)
+ if (not @insulation_assembly_r_value.nil?) || (not @insulation_cavity_r_value.nil?) || (not @insulation_continuous_r_value.nil?)
+ insulation = XMLHelper.add_element(floor, 'Insulation')
+ sys_id = XMLHelper.add_element(insulation, 'SystemIdentifier')
+ if not @insulation_id.nil?
+ XMLHelper.add_attribute(sys_id, 'id', @insulation_id)
+ else
+ XMLHelper.add_attribute(sys_id, 'id', @id + 'Insulation')
+ end
+ XMLHelper.add_element(insulation, 'InsulationGrade', @insulation_grade, :integer) unless @insulation_grade.nil?
+ XMLHelper.add_element(insulation, 'AssemblyEffectiveRValue', @insulation_assembly_r_value, :float, @insulation_assembly_r_value_isdefaulted) unless @insulation_assembly_r_value.nil?
+ if not @insulation_cavity_r_value.nil?
+ layer = XMLHelper.add_element(insulation, 'Layer')
+ XMLHelper.add_element(layer, 'InstallationType', 'cavity', :string)
+ XMLHelper.add_element(layer, 'NominalRValue', @insulation_cavity_r_value, :float)
+ end
+ if not @insulation_continuous_r_value.nil?
+ layer = XMLHelper.add_element(insulation, 'Layer')
+ XMLHelper.add_element(layer, 'InstallationType', 'continuous', :string)
+ XMLHelper.add_element(layer, 'NominalRValue', @insulation_continuous_r_value, :float)
+ end
end
+ @detailed_construction.to_oga(floor)
end
def from_oga(floor)
@@ -2983,6 +3040,7 @@ def from_oga(floor)
@insulation_cavity_r_value = XMLHelper.get_value(insulation, "Layer[InstallationType='cavity']/NominalRValue", :float)
@insulation_continuous_r_value = XMLHelper.get_value(insulation, "Layer[InstallationType='continuous']/NominalRValue", :float)
end
+ @detailed_construction.from_oga(XMLHelper.get_element(floor, 'DetailedConstruction'))
end
end
@@ -3493,6 +3551,119 @@ def from_oga(door)
end
end
+ class DetailedConstruction < BaseElement
+ def initialize(hpxml_object, *args)
+ @construction_layers = DetailedConstructionLayers.new(hpxml_object)
+ super(hpxml_object, *args)
+ end
+ ATTRS = [:id, :construction_layers]
+ attr_accessor(*ATTRS)
+
+ def check_for_errors
+ errors = []
+ return errors
+ end
+
+ def to_oga(surface)
+ return if surface.nil?
+ return if @construction_layers.empty?
+
+ detailed_construction = XMLHelper.add_element(surface, 'DetailedConstruction')
+ sys_id = XMLHelper.add_element(detailed_construction, 'SystemIdentifier')
+ XMLHelper.add_attribute(sys_id, 'id', @id)
+
+ @construction_layers.to_oga(detailed_construction)
+ end
+
+ def from_oga(detailed_construction)
+ return if detailed_construction.nil?
+
+ @id = HPXML::get_id(detailed_construction)
+ @construction_layers.from_oga(detailed_construction)
+ end
+ end
+
+ class DetailedConstructionLayers < BaseArrayElement
+ def add(**kwargs)
+ self << DetailedConstructionLayer.new(@hpxml_object, **kwargs)
+ end
+
+ def from_oga(detailed_construction)
+ return if detailed_construction.nil?
+
+ XMLHelper.get_elements(detailed_construction, 'ConstructionLayer').each do |construction_layer|
+ self << DetailedConstructionLayer.new(@hpxml_object, construction_layer)
+ end
+ end
+ end
+
+ class DetailedConstructionLayer < BaseElement
+ def initialize(hpxml_object, *args)
+ @layer_materials = DetailedConstructionLayerMaterials.new(hpxml_object)
+ super(hpxml_object, *args)
+ end
+ ATTRS = [:layer_type, :layer_thickness, :layer_materials]
+ attr_accessor(*ATTRS)
+
+ def to_oga(detailed_construction)
+ return if detailed_construction.nil?
+
+ construction_layer = XMLHelper.add_element(detailed_construction, 'ConstructionLayer')
+ XMLHelper.add_element(construction_layer, 'LayerType', @layer_type, :string) unless @layer_type.nil?
+ XMLHelper.add_element(construction_layer, 'LayerThickness', @layer_thickness, :float) unless @layer_thickness.nil?
+
+ @layer_materials.to_oga(construction_layer)
+ end
+
+ def from_oga(construction_layer)
+ return if construction_layer.nil?
+
+ @layer_type = XMLHelper.get_value(construction_layer, 'LayerType', :string)
+ @layer_thickness = XMLHelper.get_value(construction_layer, 'LayerThickness', :float)
+ @layer_materials.from_oga(construction_layer)
+ end
+ end
+
+ class DetailedConstructionLayerMaterials < BaseArrayElement
+ def add(**kwargs)
+ self << DetailedConstructionLayerMaterial.new(@hpxml_object, **kwargs)
+ end
+
+ def from_oga(construction_layer)
+ XMLHelper.get_elements(construction_layer, 'LayerMaterial').each do |layer_material|
+ self << DetailedConstructionLayerMaterial.new(@hpxml_object, layer_material)
+ end
+ end
+ end
+
+ class DetailedConstructionLayerMaterial < BaseElement
+ ATTRS = [:area_fraction, :material_type, :conductivity, :density, :specific_heat, :r_value]
+ attr_accessor(*ATTRS)
+
+ def to_oga(construction_layer)
+ return if construction_layer.nil?
+
+ layer_material = XMLHelper.add_element(construction_layer, 'LayerMaterial')
+ XMLHelper.add_element(layer_material, 'AreaFraction', @area_fraction, :float) unless @area_fraction.nil?
+ XMLHelper.add_element(layer_material, 'MaterialType', @material_type, :string) unless @material_type.nil?
+ XMLHelper.add_element(layer_material, 'Conductivity', @conductivity, :float) unless @conductivity.nil?
+ XMLHelper.add_element(layer_material, 'Density', @density, :float) unless @density.nil?
+ XMLHelper.add_element(layer_material, 'SpecificHeat', @specific_heat, :float) unless @specific_heat.nil?
+ XMLHelper.add_element(layer_material, 'RValue', @r_value, :float) unless @r_value.nil?
+ end
+
+ def from_oga(layer_material)
+ return if layer_material.nil?
+
+ @area_fraction = XMLHelper.get_value(layer_material, 'AreaFraction', :float)
+ @material_type = XMLHelper.get_value(layer_material, 'MaterialType', :string)
+ @conductivity = XMLHelper.get_value(layer_material, 'Conductivity', :float)
+ @density = XMLHelper.get_value(layer_material, 'Density', :float)
+ @specific_heat = XMLHelper.get_value(layer_material, 'SpecificHeat', :float)
+ @r_value = XMLHelper.get_value(layer_material, 'RValue', :float)
+ end
+ end
+
class PartitionWallMass < BaseElement
ATTRS = [:area_fraction, :interior_finish_type, :interior_finish_thickness]
attr_accessor(*ATTRS)
diff --git a/HPXMLtoOpenStudio/resources/hpxml_defaults.rb b/HPXMLtoOpenStudio/resources/hpxml_defaults.rb
index 1849f9a8fc..3803c26126 100644
--- a/HPXMLtoOpenStudio/resources/hpxml_defaults.rb
+++ b/HPXMLtoOpenStudio/resources/hpxml_defaults.rb
@@ -13,6 +13,8 @@ def self.apply(runner, hpxml, eri_version, weather, epw_file: nil, schedules_fil
ncfl_ag = hpxml.building_construction.number_of_conditioned_floors_above_grade
has_uncond_bsmnt = hpxml.has_location(HPXML::LocationBasementUnconditioned)
infil_measurement = Airflow.get_infiltration_measurement_of_interest(hpxml.air_infiltration_measurements)
+ apply_ashrae140_assumptions = hpxml.header.apply_ashrae140_assumptions # Hidden feature
+ apply_ashrae140_assumptions = false if apply_ashrae140_assumptions.nil?
# Check for presence of fuels once
has_fuel = {}
@@ -33,9 +35,9 @@ def self.apply(runner, hpxml, eri_version, weather, epw_file: nil, schedules_fil
apply_infiltration(hpxml, infil_measurement)
apply_attics(hpxml)
apply_foundations(hpxml)
- apply_roofs(hpxml)
- apply_rim_joists(hpxml)
- apply_walls(hpxml)
+ apply_roofs(runner, hpxml, apply_ashrae140_assumptions)
+ apply_rim_joists(runner, hpxml)
+ apply_walls(runner, hpxml, apply_ashrae140_assumptions)
apply_foundation_walls(hpxml)
apply_floors(hpxml)
apply_slabs(hpxml)
@@ -646,7 +648,7 @@ def self.apply_foundations(hpxml)
end
end
- def self.apply_roofs(hpxml)
+ def self.apply_roofs(runner, hpxml, apply_ashrae140_assumptions)
hpxml.roofs.each do |roof|
if roof.azimuth.nil?
roof.azimuth = get_azimuth_from_orientation(roof.orientation)
@@ -656,51 +658,75 @@ def self.apply_roofs(hpxml)
roof.orientation = get_orientation_from_azimuth(roof.azimuth)
roof.orientation_isdefaulted = true
end
- if roof.roof_type.nil?
- roof.roof_type = HPXML::RoofTypeAsphaltShingles
- roof.roof_type_isdefaulted = true
- end
- if roof.emittance.nil?
- roof.emittance = 0.90
- roof.emittance_isdefaulted = true
- end
- if roof.radiant_barrier.nil?
- roof.radiant_barrier = false
- roof.radiant_barrier_isdefaulted = true
- end
- if roof.radiant_barrier && roof.radiant_barrier_grade.nil?
- roof.radiant_barrier_grade = 1
- roof.radiant_barrier_grade_isdefaulted = true
- end
- if roof.roof_color.nil? && roof.solar_absorptance.nil?
- roof.roof_color = HPXML::ColorMedium
- roof.roof_color_isdefaulted = true
- end
- if roof.roof_color.nil?
- roof.roof_color = Constructions.get_default_roof_color(roof.roof_type, roof.solar_absorptance)
- roof.roof_color_isdefaulted = true
- elsif roof.solar_absorptance.nil?
- roof.solar_absorptance = Constructions.get_default_roof_solar_absorptance(roof.roof_type, roof.roof_color)
- roof.solar_absorptance_isdefaulted = true
- end
- if roof.interior_finish_type.nil?
- if HPXML::conditioned_finished_locations.include? roof.interior_adjacent_to
- roof.interior_finish_type = HPXML::InteriorFinishGypsumBoard
- else
- roof.interior_finish_type = HPXML::InteriorFinishNone
- end
- roof.interior_finish_type_isdefaulted = true
- end
- next unless roof.interior_finish_thickness.nil?
- if roof.interior_finish_type != HPXML::InteriorFinishNone
- roof.interior_finish_thickness = 0.5
- roof.interior_finish_thickness_isdefaulted = true
+ roof_pitch_in_deg = UnitConversions.convert(Math.atan(roof.pitch / 12.0), 'rad', 'deg')
+ roof.additional_properties.inside_film = Material.AirFilmRoof(roof_pitch_in_deg)
+ roof.additional_properties.outside_film = Material.AirFilmOutside
+ if apply_ashrae140_assumptions
+ roof.additional_properties.inside_film = Material.AirFilmRoofASHRAE140
+ roof.additional_properties.outside_film = Material.AirFilmOutsideASHRAE140
+ end
+
+ if roof.has_detailed_construction
+ # Detailed construction inputs
+ detailed_construction_assembly_r_value = calculate_assembly_r_value_from_detailed_construction(roof)
+ if roof.insulation_assembly_r_value.nil?
+ roof.insulation_assembly_r_value = detailed_construction_assembly_r_value
+ roof.insulation_assembly_r_value_isdefaulted = true
+ elsif (detailed_construction_assembly_r_value - roof.insulation_assembly_r_value).abs > 1
+ # Detailed construction is more than R-1 different from user-specified assembly R-value
+ if not runner.nil?
+ runner.registerWarning("For roof '#{roof.id}', assembly R-value calculated from detailed construction (#{detailed_construction_assembly_r_value}) differs from AssemblyEffectiveRValue (#{roof.insulation_assembly_r_value}). The latter will be overridden.")
+ end
+ roof.insulation_assembly_r_value = detailed_construction_assembly_r_value
+ roof.insulation_assembly_r_value_isdefaulted = true
+ end
+ else
+ # Simple inputs
+ if roof.roof_type.nil?
+ roof.roof_type = HPXML::RoofTypeAsphaltShingles
+ roof.roof_type_isdefaulted = true
+ end
+ if roof.emittance.nil?
+ roof.emittance = 0.90
+ roof.emittance_isdefaulted = true
+ end
+ if roof.radiant_barrier.nil?
+ roof.radiant_barrier = false
+ roof.radiant_barrier_isdefaulted = true
+ end
+ if roof.radiant_barrier && roof.radiant_barrier_grade.nil?
+ roof.radiant_barrier_grade = 1
+ roof.radiant_barrier_grade_isdefaulted = true
+ end
+ if roof.roof_color.nil? && roof.solar_absorptance.nil?
+ roof.roof_color = HPXML::ColorMedium
+ roof.roof_color_isdefaulted = true
+ end
+ if roof.roof_color.nil?
+ roof.roof_color = Constructions.get_default_roof_color(roof.roof_type, roof.solar_absorptance)
+ roof.roof_color_isdefaulted = true
+ elsif roof.solar_absorptance.nil?
+ roof.solar_absorptance = Constructions.get_default_roof_solar_absorptance(roof.roof_type, roof.roof_color)
+ roof.solar_absorptance_isdefaulted = true
+ end
+ if roof.interior_finish_type.nil?
+ if HPXML::conditioned_finished_locations.include? roof.interior_adjacent_to
+ roof.interior_finish_type = HPXML::InteriorFinishGypsumBoard
+ else
+ roof.interior_finish_type = HPXML::InteriorFinishNone
+ end
+ roof.interior_finish_type_isdefaulted = true
+ end
+ if roof.interior_finish_thickness.nil? && roof.interior_finish_type != HPXML::InteriorFinishNone
+ roof.interior_finish_thickness = 0.5
+ roof.interior_finish_thickness_isdefaulted = true
+ end
end
end
end
- def self.apply_rim_joists(hpxml)
+ def self.apply_rim_joists(runner, hpxml)
hpxml.rim_joists.each do |rim_joist|
if rim_joist.azimuth.nil?
rim_joist.azimuth = get_azimuth_from_orientation(rim_joist.orientation)
@@ -711,31 +737,55 @@ def self.apply_rim_joists(hpxml)
rim_joist.orientation_isdefaulted = true
end
- next unless rim_joist.is_exterior
-
- if rim_joist.emittance.nil?
- rim_joist.emittance = 0.90
- rim_joist.emittance_isdefaulted = true
- end
- if rim_joist.siding.nil?
- rim_joist.siding = HPXML::SidingTypeWood
- rim_joist.siding_isdefaulted = true
- end
- if rim_joist.color.nil? && rim_joist.solar_absorptance.nil?
- rim_joist.color = HPXML::ColorMedium
- rim_joist.color_isdefaulted = true
- end
- if rim_joist.color.nil?
- rim_joist.color = Constructions.get_default_wall_color(rim_joist.solar_absorptance)
- rim_joist.color_isdefaulted = true
- elsif rim_joist.solar_absorptance.nil?
- rim_joist.solar_absorptance = Constructions.get_default_wall_solar_absorptance(rim_joist.color)
- rim_joist.solar_absorptance_isdefaulted = true
+ rim_joist.additional_properties.inside_film = Material.AirFilmVertical
+ if rim_joist.is_exterior
+ rim_joist.additional_properties.outside_film = Material.AirFilmOutside
+ else
+ rim_joist.additional_properties.outside_film = Material.AirFilmVertical
+ end
+
+ if rim_joist.has_detailed_construction
+ # Detailed construction inputs
+ detailed_construction_assembly_r_value = calculate_assembly_r_value_from_detailed_construction(rim_joist)
+ if rim_joist.insulation_assembly_r_value.nil?
+ rim_joist.insulation_assembly_r_value = detailed_construction_assembly_r_value
+ rim_joist.insulation_assembly_r_value_isdefaulted = true
+ elsif (detailed_construction_assembly_r_value - rim_joist.insulation_assembly_r_value).abs > 1
+ # Detailed construction is more than R-1 different from user-specified assembly R-value
+ if not runner.nil?
+ runner.registerWarning("For rim joist '#{rim_joist.id}', assembly R-value calculated from detailed construction (#{detailed_construction_assembly_r_value}) differs from AssemblyEffectiveRValue (#{rim_joist.insulation_assembly_r_value}). The latter will be overridden.")
+ end
+ rim_joist.insulation_assembly_r_value = detailed_construction_assembly_r_value
+ rim_joist.insulation_assembly_r_value_isdefaulted = true
+ end
+ else
+ # Simple inputs
+ if rim_joist.is_exterior
+ if rim_joist.emittance.nil?
+ rim_joist.emittance = 0.90
+ rim_joist.emittance_isdefaulted = true
+ end
+ if rim_joist.siding.nil?
+ rim_joist.siding = HPXML::SidingTypeWood
+ rim_joist.siding_isdefaulted = true
+ end
+ if rim_joist.color.nil? && rim_joist.solar_absorptance.nil?
+ rim_joist.color = HPXML::ColorMedium
+ rim_joist.color_isdefaulted = true
+ end
+ if rim_joist.color.nil?
+ rim_joist.color = Constructions.get_default_wall_color(rim_joist.solar_absorptance)
+ rim_joist.color_isdefaulted = true
+ elsif rim_joist.solar_absorptance.nil?
+ rim_joist.solar_absorptance = Constructions.get_default_wall_solar_absorptance(rim_joist.color)
+ rim_joist.solar_absorptance_isdefaulted = true
+ end
+ end
end
end
end
- def self.apply_walls(hpxml)
+ def self.apply_walls(runner, hpxml, apply_ashrae140_assumptions)
hpxml.walls.each do |wall|
if wall.azimuth.nil?
wall.azimuth = get_azimuth_from_orientation(wall.orientation)
@@ -746,40 +796,66 @@ def self.apply_walls(hpxml)
wall.orientation_isdefaulted = true
end
+ wall.additional_properties.inside_film = Material.AirFilmVertical
if wall.is_exterior
- if wall.emittance.nil?
- wall.emittance = 0.90
- wall.emittance_isdefaulted = true
- end
- if wall.siding.nil?
- wall.siding = HPXML::SidingTypeWood
- wall.siding_isdefaulted = true
+ wall.additional_properties.outside_film = Material.AirFilmOutside
+ else
+ wall.additional_properties.outside_film = Material.AirFilmVertical
+ end
+ if apply_ashrae140_assumptions
+ wall.additional_properties.inside_film = Material.AirFilmVerticalASHRAE140
+ wall.additional_properties.outside_film = Material.AirFilmOutsideASHRAE140
+ end
+
+ if wall.has_detailed_construction
+ # Detailed construction inputs
+ detailed_construction_assembly_r_value = calculate_assembly_r_value_from_detailed_construction(wall)
+ if wall.insulation_assembly_r_value.nil?
+ wall.insulation_assembly_r_value = detailed_construction_assembly_r_value
+ wall.insulation_assembly_r_value_isdefaulted = true
+ elsif (detailed_construction_assembly_r_value - wall.insulation_assembly_r_value).abs > 1
+ # Detailed construction is more than R-1 different from user-specified assembly R-value
+ if not runner.nil?
+ runner.registerWarning("For wall '#{wall.id}', assembly R-value calculated from detailed construction (#{detailed_construction_assembly_r_value}) differs from AssemblyEffectiveRValue (#{wall.insulation_assembly_r_value}). The latter will be overridden.")
+ end
+ wall.insulation_assembly_r_value = detailed_construction_assembly_r_value
+ wall.insulation_assembly_r_value_isdefaulted = true
end
- if wall.color.nil? && wall.solar_absorptance.nil?
- wall.color = HPXML::ColorMedium
- wall.color_isdefaulted = true
+ else
+ # Simple inputs
+ if wall.is_exterior
+ if wall.emittance.nil?
+ wall.emittance = 0.90
+ wall.emittance_isdefaulted = true
+ end
+ if wall.siding.nil?
+ wall.siding = HPXML::SidingTypeWood
+ wall.siding_isdefaulted = true
+ end
+ if wall.color.nil? && wall.solar_absorptance.nil?
+ wall.color = HPXML::ColorMedium
+ wall.color_isdefaulted = true
+ end
+ if wall.color.nil?
+ wall.color = Constructions.get_default_wall_color(wall.solar_absorptance)
+ wall.color_isdefaulted = true
+ elsif wall.solar_absorptance.nil?
+ wall.solar_absorptance = Constructions.get_default_wall_solar_absorptance(wall.color)
+ wall.solar_absorptance_isdefaulted = true
+ end
end
- if wall.color.nil?
- wall.color = Constructions.get_default_wall_color(wall.solar_absorptance)
- wall.color_isdefaulted = true
- elsif wall.solar_absorptance.nil?
- wall.solar_absorptance = Constructions.get_default_wall_solar_absorptance(wall.color)
- wall.solar_absorptance_isdefaulted = true
+ if wall.interior_finish_type.nil?
+ if HPXML::conditioned_finished_locations.include? wall.interior_adjacent_to
+ wall.interior_finish_type = HPXML::InteriorFinishGypsumBoard
+ else
+ wall.interior_finish_type = HPXML::InteriorFinishNone
+ end
+ wall.interior_finish_type_isdefaulted = true
end
- end
- if wall.interior_finish_type.nil?
- if HPXML::conditioned_finished_locations.include? wall.interior_adjacent_to
- wall.interior_finish_type = HPXML::InteriorFinishGypsumBoard
- else
- wall.interior_finish_type = HPXML::InteriorFinishNone
+ if wall.interior_finish_thickness.nil? && wall.interior_finish_type != HPXML::InteriorFinishNone
+ wall.interior_finish_thickness = 0.5
+ wall.interior_finish_thickness_isdefaulted = true
end
- wall.interior_finish_type_isdefaulted = true
- end
- next unless wall.interior_finish_thickness.nil?
-
- if wall.interior_finish_type != HPXML::InteriorFinishNone
- wall.interior_finish_thickness = 0.5
- wall.interior_finish_thickness_isdefaulted = true
end
end
end
@@ -2903,4 +2979,11 @@ def self.get_nbeds_adjusted_for_operational_calculation(hpxml)
fail "Unexpected residential facility type: #{unit_type}."
end
end
+
+ def self.calculate_assembly_r_value_from_detailed_construction(surface)
+ constr = Constructions.create_from_detailed_construction(surface.detailed_construction,
+ surface.additional_properties.inside_film,
+ surface.additional_properties.outside_film)
+ return constr.assembly_rvalue.round(1)
+ end
end
diff --git a/HPXMLtoOpenStudio/resources/hpxml_schema/HPXML.xsd b/HPXMLtoOpenStudio/resources/hpxml_schema/HPXML.xsd
index 43f97d950c..1bd1ca7d51 100644
--- a/HPXMLtoOpenStudio/resources/hpxml_schema/HPXML.xsd
+++ b/HPXMLtoOpenStudio/resources/hpxml_schema/HPXML.xsd
@@ -200,6 +200,65 @@
+
+
+ Can be used to describe properties for each individual material in the construction
+
+
+
+
+
+ Layers should be ordered from exterior to interior. Do not include air films.
+
+
+
+
+
+
+ [in]
+
+
+
+
+
+
+
+ Fraction of the layer's area defined by this material. Values across all materials in a layer should sum to 1.
+
+
+
+
+ E.g., "wood", "concrete", "drywall", etc.
+
+
+
+
+ [Btu/hr-ft-F]
+
+
+
+
+ [lb/ft3]
+
+
+
+
+ [Btu/lb-F]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -989,6 +1048,7 @@
+
@@ -1029,6 +1089,7 @@
+
@@ -1082,6 +1143,7 @@
+
@@ -1149,6 +1211,7 @@
+
@@ -1188,6 +1251,7 @@
+
Indicate whether the floor section is a mobile home belly or wing section. Omit if not applicable. For a mobile home, if the foundation type is a belly and wing, ExteriorAdjacentTo should be set to 'outside'.
@@ -1247,6 +1311,7 @@
+
@@ -4136,6 +4201,11 @@
+
+
+ Describes ducts buried in, e.g., attic loose-fill insulation. Partially buried ducts have insulation that does not cover the top of the ducts. Fully buried ducts have insulation that just covers the top of the ducts. Deeply buried ducts have insulation that continues above the top of the ducts. See https://basc.pnnl.gov/resource-guides/ducts-buried-attic-insulation for more information.
+
+
@@ -6403,6 +6473,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
@@ -8357,6 +8439,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -9707,6 +9804,19 @@
+
+
+ A fraction that has to be between 0 (exclusive) and 1 (inclusive)
+
+
+
+
+
+
+
+
+
+
@@ -11375,4 +11485,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/HPXMLtoOpenStudio/resources/hpxml_schematron/EPvalidator.xml b/HPXMLtoOpenStudio/resources/hpxml_schematron/EPvalidator.xml
index 1c7590abd9..0aef29839b 100644
--- a/HPXMLtoOpenStudio/resources/hpxml_schematron/EPvalidator.xml
+++ b/HPXMLtoOpenStudio/resources/hpxml_schematron/EPvalidator.xml
@@ -413,7 +413,7 @@
Expected 0 or 1 element(s) for xpath: InteriorFinish/Thickness
Expected 1 element(s) for xpath: Pitch
Expected 0 or 1 element(s) for xpath: RadiantBarrier
- Expected 1 element(s) for xpath: Insulation/AssemblyEffectiveRValue
+ Expected 1 or more element(s) for xpath: Insulation/AssemblyEffectiveRValue | DetailedConstruction
@@ -444,7 +444,7 @@
Expected Siding to be 'wood siding' or 'vinyl siding' or 'stucco' or 'fiber cement siding' or 'brick veneer' or 'aluminum siding' or 'masonite siding' or 'composite shingle siding' or 'asbestos siding' or 'synthetic stucco' or 'none'
Expected 0 or more element(s) for xpath: Color | SolarAbsorptance
Expected 0 or 1 element(s) for xpath: Emittance
- Expected 1 element(s) for xpath: Insulation/AssemblyEffectiveRValue
+ Expected 1 or more element(s) for xpath: Insulation/AssemblyEffectiveRValue | DetailedConstruction
@@ -465,7 +465,7 @@
Expected 0 or 1 element(s) for xpath: InteriorFinish/Type
Expected InteriorFinish/Type to be 'gypsum board' or 'gypsum composite board' or 'plaster' or 'wood' or 'none'
Expected 0 or 1 element(s) for xpath: InteriorFinish/Thickness
- Expected 1 element(s) for xpath: Insulation/AssemblyEffectiveRValue
+ Expected 1 or more element(s) for xpath: Insulation/AssemblyEffectiveRValue | DetailedConstruction
@@ -489,6 +489,7 @@
Expected 1 element(s) for xpath: DepthBelowGrade
Expected DepthBelowGrade to be less than or equal to Height
+
Expected 1 element(s) for xpath: Insulation/Layer[InstallationType[text()="continuous - interior"]] | Insulation/AssemblyEffectiveRValue
Expected 1 element(s) for xpath: Insulation/Layer[InstallationType[text()="continuous - exterior"]] | Insulation/AssemblyEffectiveRValue
@@ -527,7 +528,7 @@
Expected 0 or 1 element(s) for xpath: InteriorFinish/Type
Expected InteriorFinish/Type to be 'gypsum board' or 'gypsum composite board' or 'plaster' or 'wood' or 'none'
Expected 0 or 1 element(s) for xpath: InteriorFinish/Thickness
- Expected 1 element(s) for xpath: Insulation/AssemblyEffectiveRValue
+ Expected 1 or more element(s) for xpath: Insulation/AssemblyEffectiveRValue | DetailedConstruction
@@ -678,6 +679,38 @@
+
+ [DetailedConstruction]
+
+ Expected 1 or more element(s) for xpath: ConstructionLayer
+
+
+
+
+ [DetailedConstructionLayer]
+
+ Expected 1 or more element(s) for xpath: LayerMaterial
+ Expected sum(LayerMaterial/AreaFraction) to be 1
+
+
+
+
+ [DetailedConstructionLayerMaterial]
+
+ Expected 1 element(s) for xpath: AreaFraction
+ Expected 1 or more element(s) for xpath: Conductivity | RValue
+
+
+
+
+ [DetailedConstructionLayerMaterial=Detailed]
+
+ Expected 1 element(s) for xpath: ../LayerThickness
+ Expected 1 element(s) for xpath: Density
+ Expected 1 element(s) for xpath: SpecificHeat
+
+
+
[PartitionWallMass]
diff --git a/HPXMLtoOpenStudio/resources/hvac_sizing.rb b/HPXMLtoOpenStudio/resources/hvac_sizing.rb
index 1752fbafe1..f87fd529e8 100644
--- a/HPXMLtoOpenStudio/resources/hvac_sizing.rb
+++ b/HPXMLtoOpenStudio/resources/hvac_sizing.rb
@@ -745,14 +745,19 @@ def self.process_load_walls(bldg_design_loads)
wall_area = wall.net_area
end
+ solar_absorptance = wall.solar_absorptance
+ if solar_absorptance.nil?
+ solar_absorptance = 0.9 # FIXME: Temporary
+ end
+
azimuths.each do |azimuth|
if wall.is_exterior
# Adjust base Cooling Load Temperature Difference (CLTD)
# Assume absorptivity for light walls < 0.5, medium walls <= 0.75, dark walls > 0.75 (based on MJ8 Table 4B Notes)
- if wall.solar_absorptance <= 0.5
+ if solar_absorptance <= 0.5
colorMultiplier = 0.65 # MJ8 Table 4B Notes, pg 348
- elsif wall.solar_absorptance <= 0.75
+ elsif solar_absorptance <= 0.75
colorMultiplier = 0.83 # MJ8 Appendix 12, pg 519
else
colorMultiplier = 1.0
@@ -2483,10 +2488,19 @@ def self.get_wall_group(wall)
end
wall_ufactor = 1.0 / wall.insulation_assembly_r_value
+ if not wall.siding.nil?
+ wall_siding_is_brick = (wall.siding == HPXML::SidingTypeBrick)
+ elsif wall.has_detailed_construction
+ # Assume brick if at least 50% of the outermost construction layer is very high density
+ exterior_layer = wall.detailed_construction.construction_layers[0]
+ wall_siding_is_brick = exterior_layer.layer_materials.any? { |m| m.area_fraction >= 0.5 && m.density >= 100.0 }
+ else
+ wall_siding_is_brick = false
+ end
# The following correlations were estimated by analyzing MJ8 construction tables.
if wall_type == HPXML::WallTypeWoodStud
- if wall.siding == HPXML::SidingTypeBrick
+ if wall_siding_is_brick
if wall_ufactor <= 0.070
wall_group = 11 # K
elsif wall_ufactor <= 0.083
@@ -2527,7 +2541,7 @@ def self.get_wall_group(wall)
end
elsif wall_type == HPXML::WallTypeSteelStud
- if wall.siding == HPXML::SidingTypeBrick
+ if wall_siding_is_brick
if wall_ufactor <= 0.090
wall_group = 11 # K
elsif wall_ufactor <= 0.105
@@ -2569,20 +2583,20 @@ def self.get_wall_group(wall)
elsif wall_type == HPXML::WallTypeDoubleWoodStud
wall_group = 10 # J (assumed since MJ8 does not include double stud constructions)
- if wall.siding == HPXML::SidingTypeBrick
+ if wall_siding_is_brick
wall_group = 11 # K
end
elsif wall_type == HPXML::WallTypeSIP
# Manual J refers to SIPs as Structural Foam Panel (SFP)
if wall_ufactor >= (0.072 + 0.050) / 2
- if wall.siding == HPXML::SidingTypeBrick
+ if wall_siding_is_brick
wall_group = 10 # J
else
wall_group = 7 # G
end
elsif wall_ufactor >= 0.050
- if wall.siding == HPXML::SidingTypeBrick
+ if wall_siding_is_brick
wall_group = 11 # K
else
wall_group = 9 # I
@@ -3187,10 +3201,10 @@ def initialize
class Numeric
def deg2rad
- self * Math::PI / 180
+ self * UnitConversions.convert(1.0, 'deg', 'rad')
end
def rad2deg
- self * 180 / Math::PI
+ self * UnitConversions.convert(1.0, 'rad', 'deg')
end
end
diff --git a/HPXMLtoOpenStudio/resources/materials.rb b/HPXMLtoOpenStudio/resources/materials.rb
index 012f0d774e..8aba8363ed 100644
--- a/HPXMLtoOpenStudio/resources/materials.rb
+++ b/HPXMLtoOpenStudio/resources/materials.rb
@@ -424,6 +424,21 @@ def self.StrawBale
end
end
+class NoMassMaterial
+ # name - NoMassMaterial name
+ # rvalue - R-value (h-ft^2-F/Btu)
+ # tAbs - thermal absorptance (emittance); 0.9 is EnergyPlus default
+ # sAbs - solar absorptance; 0.7 is EnergyPlus default
+ def initialize(name: nil, rvalue:, tAbs: 0.9, sAbs: 0.7)
+ @name = name
+ @rvalue = rvalue
+ @tAbs = tAbs
+ @sAbs = sAbs
+ end
+
+ attr_accessor :name, :rvalue, :tAbs, :sAbs
+end
+
class GlazingMaterial
def initialize(name:, ufactor:, shgc:)
@name = name
diff --git a/HPXMLtoOpenStudio/tests/test_validation.rb b/HPXMLtoOpenStudio/tests/test_validation.rb
index 0b13ea53e7..5ca84dcbc1 100644
--- a/HPXMLtoOpenStudio/tests/test_validation.rb
+++ b/HPXMLtoOpenStudio/tests/test_validation.rb
@@ -1218,6 +1218,9 @@ def test_ruby_error_messages
def test_ruby_warning_messages
# Test case => Error message
all_expected_warnings = { 'cfis-undersized-supplemental-fan' => ["CFIS supplemental fan 'VentilationFan2' is undersized (90.0 cfm) compared to the target hourly ventilation rate (110.0 cfm)."],
+ 'detailed-construction-assembly-rvalue' => ["For rim joist 'RimJoist1', assembly R-value calculated from detailed construction (22.9) differs from AssemblyEffectiveRValue (10.0). The latter will be overridden.",
+ "For wall 'Wall1', assembly R-value calculated from detailed construction (22.9) differs from AssemblyEffectiveRValue (10.0). The latter will be overridden.",
+ "For roof 'Roof1', assembly R-value calculated from detailed construction (2.3) differs from AssemblyEffectiveRValue (10.0). The latter will be overridden."],
'hvac-setpoint-adjustments' => ['HVAC setpoints have been automatically adjusted to prevent periods where the heating setpoint is greater than the cooling setpoint.'],
'hvac-setpoint-adjustments-daily-setbacks' => ['HVAC setpoints have been automatically adjusted to prevent periods where the heating setpoint is greater than the cooling setpoint.'],
'hvac-setpoint-adjustments-daily-schedules' => ['HVAC setpoints have been automatically adjusted to prevent periods where the heating setpoint is greater than the cooling setpoint.'],
@@ -1299,6 +1302,11 @@ def test_ruby_warning_messages
hpxml = HPXML.new(hpxml_path: File.join(@sample_files_path, 'base-mechvent-cfis-supplemental-fan-exhaust.xml'))
suppl_fan = hpxml.ventilation_fans.find { |f| f.is_cfis_supplemental_fan? }
suppl_fan.tested_flow_rate = 90.0
+ elsif ['detailed-construction-assembly-rvalue'].include? warning_case
+ hpxml = HPXML.new(hpxml_path: File.join(@sample_files_path, 'base-enclosure-detailed-constructions.xml'))
+ hpxml.walls[0].insulation_assembly_r_value = 10
+ hpxml.rim_joists[0].insulation_assembly_r_value = 10
+ hpxml.roofs[0].insulation_assembly_r_value = 10
elsif ['hvac-setpoint-adjustments'].include? warning_case
hpxml = HPXML.new(hpxml_path: File.join(@sample_files_path, 'base.xml'))
hpxml.hvac_controls[0].heating_setpoint_temp = 76.0
diff --git a/tasks.rb b/tasks.rb
index ebce3d5a2d..88eb92e784 100644
--- a/tasks.rb
+++ b/tasks.rb
@@ -1164,6 +1164,68 @@ def apply_hpxml_modification(hpxml_file, hpxml)
azimuth: 180,
r_value: 4.4)
end
+ if ['base-enclosure-detailed-constructions.xml'].include? hpxml_file
+ # Wall 1 (exterior): R-36 Closed Cell Spray Foam, 2x6, 24 in o.c.
+ # Wall 2 (attic gable): Uninsulated, 2x6, 24 in o.c., open cavity
+ # RimJoist 1: Same as Wall 1
+ (hpxml.walls + hpxml.rim_joists).each_with_index do |surface, i|
+ surface.insulation_assembly_r_value = nil
+ surface.siding = nil
+ surface.solar_absorptance = nil
+ surface.emittance = nil
+ surface.interior_finish_type = nil if surface.is_a? HPXML::Wall
+ surface.detailed_construction.id = surface.id.gsub('Wall', 'WallConstruction').gsub('RimJoist', 'RimJoistConstruction')
+ surface.detailed_construction.construction_layers.add(layer_thickness: 0.375)
+ surface.detailed_construction.construction_layers[-1].layer_materials.add(area_fraction: 1.0,
+ material_type: 'vinyl siding',
+ conductivity: 0.052,
+ density: 11.1,
+ specific_heat: 0.25)
+ surface.detailed_construction.construction_layers.add(layer_thickness: 0.5)
+ surface.detailed_construction.construction_layers[-1].layer_materials.add(area_fraction: 1.0,
+ material_type: 'osb',
+ conductivity: 0.067,
+ density: 32.0,
+ specific_heat: 0.29)
+ if (i == 0) || (i == 2)
+ surface.detailed_construction.construction_layers.add(layer_thickness: 5.5)
+ surface.detailed_construction.construction_layers[-1].layer_materials.add(area_fraction: 0.22,
+ material_type: 'wood stud',
+ conductivity: 0.067,
+ density: 32.0,
+ specific_heat: 0.29)
+ surface.detailed_construction.construction_layers[-1].layer_materials.add(area_fraction: 0.78,
+ material_type: 'spray foam',
+ r_value: 36.0,
+ density: 2.85,
+ specific_heat: 0.25)
+ end
+ surface.detailed_construction.construction_layers.add(layer_thickness: 0.5)
+ surface.detailed_construction.construction_layers[-1].layer_materials.add(area_fraction: 1.0,
+ material_type: 'gypsum board',
+ conductivity: 0.093,
+ density: 50.0,
+ specific_heat: 0.2)
+ end
+ # Roof: Uninsulated, asphalt shingles
+ surface = hpxml.roofs[0]
+ surface.insulation_assembly_r_value = nil
+ surface.roof_type = nil
+ surface.solar_absorptance = nil
+ surface.emittance = nil
+ surface.radiant_barrier = nil
+ surface.detailed_construction.id = surface.id.gsub('Roof', 'RoofConstruction')
+ surface.detailed_construction.construction_layers.add()
+ surface.detailed_construction.construction_layers[-1].layer_materials.add(area_fraction: 1.0,
+ material_type: 'asphalt shingles',
+ r_value: 0.44)
+ surface.detailed_construction.construction_layers.add(layer_thickness: 0.75)
+ surface.detailed_construction.construction_layers[-1].layer_materials.add(area_fraction: 1.0,
+ material_type: 'osb',
+ conductivity: 0.067,
+ density: 32.0,
+ specific_heat: 0.29)
+ end
# ---------- #
# HPXML HVAC #
diff --git a/workflow/hpxml_inputs.json b/workflow/hpxml_inputs.json
index 31e0a8c90a..5f9fcd6f55 100644
--- a/workflow/hpxml_inputs.json
+++ b/workflow/hpxml_inputs.json
@@ -1336,6 +1336,9 @@
"sample_files/base-enclosure-ceilingtypes.xml": {
"parent_hpxml": "sample_files/base.xml"
},
+ "sample_files/base-enclosure-detailed-constructions.xml": {
+ "parent_hpxml": "sample_files/base.xml"
+ },
"sample_files/base-enclosure-floortypes.xml": {
"parent_hpxml": "sample_files/base-foundation-ambient.xml"
},
diff --git a/workflow/sample_files/base-enclosure-detailed-constructions.xml b/workflow/sample_files/base-enclosure-detailed-constructions.xml
new file mode 100644
index 0000000000..0854d46766
--- /dev/null
+++ b/workflow/sample_files/base-enclosure-detailed-constructions.xml
@@ -0,0 +1,674 @@
+
+
+
+ HPXML
+ tasks.rb
+ 2000-01-01T00:00:00-07:00
+ create
+
+
+
+
+ 60
+
+
+
+ Bills
+
+
+
+
+
+
+
+
+
+ CO
+
+
+
+ proposed workscope
+
+
+
+
+ suburban
+ stand-alone
+ no units above or below
+ 180
+
+ electricity
+ natural gas
+
+
+
+ single-family detached
+ 2.0
+ 1.0
+ 8.0
+ 3
+ 2
+ 2700.0
+ 21600.0
+
+
+
+
+ 2006
+ 5B
+
+
+
+ USA_CO_Denver.Intl.AP.725650_TMY3
+
+ USA_CO_Denver.Intl.AP.725650_TMY3.epw
+
+
+
+
+
+
+
+ 50.0
+
+ ACH
+ 3.0
+
+ 21600.0
+
+
+
+
+
+
+
+ false
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+ attic - unvented
+ 1509.3
+ 6.0
+
+
+
+
+ 1.0
+ asphalt shingles
+ 0.44
+
+
+
+ 0.75
+
+ 1.0
+ osb
+ 0.067
+ 32.0
+ 0.29
+
+
+
+
+
+
+
+
+ outside
+ basement - conditioned
+ 115.6
+
+
+
+ 0.375
+
+ 1.0
+ vinyl siding
+ 0.052
+ 11.1
+ 0.25
+
+
+
+ 0.5
+
+ 1.0
+ osb
+ 0.067
+ 32.0
+ 0.29
+
+
+
+ 5.5
+
+ 0.22
+ wood stud
+ 0.067
+ 32.0
+ 0.29
+
+
+ 0.78
+ spray foam
+ 2.85
+ 0.25
+ 36.0
+
+
+
+ 0.5
+
+ 1.0
+ gypsum board
+ 0.093
+ 50.0
+ 0.2
+
+
+
+
+
+
+
+
+ outside
+ living space
+
+
+
+ 1200.0
+
+
+
+ 0.375
+
+ 1.0
+ vinyl siding
+ 0.052
+ 11.1
+ 0.25
+
+
+
+ 0.5
+
+ 1.0
+ osb
+ 0.067
+ 32.0
+ 0.29
+
+
+
+ 5.5
+
+ 0.22
+ wood stud
+ 0.067
+ 32.0
+ 0.29
+
+
+ 0.78
+ spray foam
+ 2.85
+ 0.25
+ 36.0
+
+
+
+ 0.5
+
+ 1.0
+ gypsum board
+ 0.093
+ 50.0
+ 0.2
+
+
+
+
+
+
+ outside
+ attic - unvented
+ gable
+
+
+
+ 225.0
+
+
+
+ 0.375
+
+ 1.0
+ vinyl siding
+ 0.052
+ 11.1
+ 0.25
+
+
+
+ 0.5
+
+ 1.0
+ osb
+ 0.067
+ 32.0
+ 0.29
+
+
+
+ 0.5
+
+ 1.0
+ gypsum board
+ 0.093
+ 50.0
+ 0.2
+
+
+
+
+
+
+
+
+ ground
+ basement - conditioned
+ 8.0
+ 1200.0
+ 8.0
+ 7.0
+
+ gypsum board
+
+
+
+
+ continuous - exterior
+ 8.9
+ 0.0
+ 8.0
+
+
+ continuous - interior
+ 0.0
+
+
+
+
+
+
+
+ attic - unvented
+ living space
+ ceiling
+
+
+
+ 1350.0
+
+ gypsum board
+
+
+
+ 39.3
+
+
+
+
+
+
+ basement - conditioned
+ 1350.0
+ 4.0
+ 150.0
+
+
+
+ 0.0
+ 0.0
+
+
+
+
+
+ 0.0
+ 0.0
+
+
+
+ 0.0
+ 0.0
+
+
+
+
+
+
+ 108.0
+ 0
+ 0.33
+ 0.45
+
+
+ 0.7
+ 0.85
+
+ 0.67
+
+
+
+
+ 72.0
+ 90
+ 0.33
+ 0.45
+
+
+ 0.7
+ 0.85
+
+ 0.67
+
+
+
+
+ 108.0
+ 180
+ 0.33
+ 0.45
+
+
+ 0.7
+ 0.85
+
+ 0.67
+
+
+
+
+ 72.0
+ 270
+ 0.33
+ 0.45
+
+
+ 0.7
+ 0.85
+
+ 0.67
+
+
+
+
+
+
+
+ 40.0
+ 180
+ 4.4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ natural gas
+ 36000.0
+
+ AFUE
+ 0.92
+
+ 1.0
+
+
+
+
+ central air conditioner
+ electricity
+ 24000.0
+ single stage
+ 1.0
+
+ SEER
+ 13.0
+
+ 0.73
+
+
+
+
+ 68.0
+ 78.0
+
+
+
+
+
+ regular velocity
+
+ supply
+
+ CFM25
+ 75.0
+ to outside
+
+
+
+ return
+
+ CFM25
+ 25.0
+ to outside
+
+
+
+
+ supply
+ 4.0
+ attic - unvented
+ 150.0
+
+
+
+ return
+ 0.0
+ attic - unvented
+ 50.0
+
+
+
+
+
+
+
+
+ electricity
+ storage water heater
+ living space
+ 40.0
+ 1.0
+ 18767.0
+ 0.95
+ 125.0
+
+
+
+
+
+ 50.0
+
+
+
+ 0.0
+
+
+
+
+ shower head
+ true
+
+
+
+ faucet
+ false
+
+
+
+
+
+
+ living space
+ 1.21
+ 380.0
+ 0.12
+ 1.09
+ 27.0
+ 6.0
+ 3.2
+
+
+
+ living space
+ electricity
+ 3.73
+ true
+ 150.0
+
+
+
+ living space
+ 307.0
+ 12
+ 0.12
+ 1.09
+ 22.32
+ 4.0
+
+
+
+ living space
+ 650.0
+ true
+
+
+
+ living space
+ electricity
+ false
+
+
+
+ false
+
+
+
+
+
+ interior
+ 0.4
+
+
+
+
+
+
+ interior
+ 0.1
+
+
+
+
+
+
+ interior
+ 0.25
+
+
+
+
+
+
+ exterior
+ 0.4
+
+
+
+
+
+
+ exterior
+ 0.1
+
+
+
+
+
+
+ exterior
+ 0.25
+
+
+
+
+
+
+
+
+ TV other
+
+ kWh/year
+ 620.0
+
+
+
+
+ other
+
+ kWh/year
+ 2457.0
+
+
+ 0.855
+ 0.045
+
+
+
+
+
+
\ No newline at end of file
diff --git a/workflow/tests/base_results/results.csv b/workflow/tests/base_results/results.csv
index fecb226025..77d706966a 100644
--- a/workflow/tests/base_results/results.csv
+++ b/workflow/tests/base_results/results.csv
@@ -124,6 +124,7 @@ base-enclosure-beds-2.xml,57.09,57.09,33.258,33.258,23.832,0.0,0.0,0.0,0.0,0.0,0
base-enclosure-beds-4.xml,60.372,60.372,38.533,38.533,21.839,0.0,0.0,0.0,0.0,0.0,0.0,0.36,0.0,0.0,4.432,0.862,10.889,0.0,0.0,4.51,0.0,0.334,0.0,0.0,0.0,0.0,2.22,0.0,0.0,0.376,0.421,1.744,1.661,0.0,2.35,8.374,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,21.839,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,20.452,0.0,14.56,11.089,0.614,0.0,0.0,0.0,0.0,2192.9,3501.7,22.758,18.225,0.0,3.555,3.64,0.512,7.518,0.629,10.513,-12.551,0.0,0.0,0.0,8.286,-0.06,4.805,0.0,0.729,0.0,4.742,-9.713,-2.498,0.0,-0.061,-0.469,-0.053,2.662,-0.028,-1.969,11.732,0.0,0.0,0.0,-6.384,-0.056,-1.182,-3.147,-0.167,0.0,3.185,8.666,2.012,1562.4,1177.9,13955.4,2960.3,36000.0,24000.0,0.0,6.8,91.76,31792.0,8583.0,7508.0,0.0,575.0,6409.0,0.0,0.0,1949.0,2171.0,4597.0,19030.0,5341.0,7037.0,0.0,207.0,265.0,0.0,0.0,0.0,2010.0,619.0,3550.0,160.0,0.0,-840.0,1000.0
base-enclosure-beds-5.xml,61.997,61.997,41.137,41.137,20.859,0.0,0.0,0.0,0.0,0.0,0.0,0.344,0.0,0.0,4.596,0.902,12.591,0.0,0.0,4.51,0.0,0.334,0.0,0.0,0.0,0.0,2.22,0.0,0.0,0.434,0.477,1.976,1.794,0.0,2.585,8.374,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,20.859,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,19.534,0.0,15.156,12.918,0.613,0.0,0.0,0.0,0.0,2561.6,3797.8,22.461,18.551,0.0,3.566,3.644,0.513,7.535,0.63,10.529,-12.537,0.0,0.0,0.0,8.301,-0.063,4.808,0.0,0.731,0.0,4.545,-10.52,-2.496,0.0,-0.08,-0.484,-0.055,2.615,-0.032,-2.014,11.746,0.0,0.0,0.0,-6.454,-0.059,-1.197,-3.228,-0.169,0.0,3.274,9.46,2.013,1770.0,1358.2,16511.3,3258.4,36000.0,24000.0,0.0,6.8,91.76,31792.0,8583.0,7508.0,0.0,575.0,6409.0,0.0,0.0,1949.0,2171.0,4597.0,19257.0,5338.0,7037.0,0.0,207.0,265.0,0.0,0.0,0.0,2010.0,619.0,3780.0,360.0,0.0,-840.0,1200.0
base-enclosure-ceilingtypes.xml,74.877,74.877,36.44,36.44,38.437,0.0,0.0,0.0,0.0,0.0,0.0,0.634,0.0,0.0,4.485,0.877,9.168,0.0,0.0,4.51,0.0,0.334,0.0,0.0,0.0,0.0,2.22,0.0,0.0,0.319,0.365,1.513,1.528,0.0,2.114,8.374,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,38.437,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,35.991,0.0,14.849,9.233,0.618,0.0,0.0,0.0,0.0,2163.1,3369.0,29.367,18.642,0.0,17.257,3.59,0.505,7.246,0.62,10.388,-12.681,0.0,0.0,0.0,7.733,-0.076,4.851,0.0,0.734,0.0,7.068,-9.079,-2.545,0.0,0.155,-0.318,-0.031,2.91,0.01,-1.499,11.603,0.0,0.0,0.0,-6.072,-0.066,-1.008,-2.883,-0.139,0.0,2.725,7.703,1.964,1354.8,997.6,11399.5,2615.8,36000.0,24000.0,0.0,6.8,91.76,44054.0,8851.0,7508.0,0.0,575.0,6409.0,0.0,0.0,1949.0,14165.0,4597.0,30006.0,5442.0,7037.0,0.0,207.0,265.0,0.0,0.0,0.0,13116.0,619.0,3320.0,0.0,0.0,0.0,0.0
+base-enclosure-detailed-constructions.xml,58.893,58.893,35.974,35.974,22.92,0.0,0.0,0.0,0.0,0.0,0.0,0.378,0.0,0.0,4.322,0.834,9.164,0.0,0.0,4.51,0.0,0.334,0.0,0.0,0.0,0.0,2.22,0.0,0.0,0.319,0.365,1.513,1.528,0.0,2.114,8.374,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,22.92,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,21.464,0.0,14.157,9.233,0.614,0.0,0.0,0.0,0.0,2132.0,3374.2,23.308,18.57,0.0,3.533,3.646,0.513,7.507,0.629,10.515,-12.551,0.0,0.0,0.0,8.288,-0.066,4.807,0.0,0.728,0.0,4.975,-8.907,-2.499,0.0,-0.02,-0.421,-0.045,2.709,-0.024,-1.915,11.732,0.0,0.0,0.0,-6.31,-0.061,-1.164,-3.055,-0.165,0.0,3.184,7.871,2.01,1354.8,997.6,11399.5,2615.8,36000.0,24000.0,0.0,6.8,91.76,31803.0,8583.0,7508.0,0.0,575.0,6420.0,0.0,0.0,1949.0,2171.0,4597.0,18911.0,5330.0,7037.0,0.0,207.0,388.0,0.0,0.0,0.0,2010.0,619.0,3320.0,0.0,0.0,0.0,0.0
base-enclosure-floortypes.xml,64.413,64.413,29.406,29.406,35.008,0.0,0.0,0.0,0.0,0.0,0.0,0.578,0.0,0.0,3.665,0.666,9.366,0.0,0.0,2.647,0.0,0.238,0.0,0.0,0.0,0.0,2.22,0.0,0.0,0.319,0.365,1.513,1.528,0.0,2.114,4.187,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,35.008,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,32.788,0.0,11.166,9.342,0.619,0.0,0.0,0.0,0.0,1757.2,3480.6,27.934,20.789,0.0,3.519,3.647,0.0,0.0,0.654,9.893,-12.804,0.0,0.0,26.42,0.0,-0.18,2.047,0.0,0.786,0.0,7.347,-7.418,-1.565,0.0,0.393,-0.069,0.0,0.0,0.096,0.25,11.037,0.0,0.0,-7.529,0.0,-0.176,-0.275,-1.935,-0.093,0.0,2.755,5.786,1.082,1354.8,997.6,11399.5,2808.9,36000.0,24000.0,0.0,6.8,91.76,37636.0,8721.0,7508.0,0.0,575.0,2198.0,0.0,14165.0,0.0,2171.0,2299.0,19985.0,5355.0,7037.0,0.0,207.0,232.0,0.0,1515.0,0.0,2010.0,310.0,3320.0,380.0,0.0,-420.0,800.0
base-enclosure-garage.xml,59.052,59.052,34.589,34.589,24.463,0.0,0.0,0.0,0.0,0.0,0.0,0.404,0.0,0.0,2.98,0.521,9.266,0.0,0.0,4.51,0.142,0.334,0.0,0.0,0.0,0.0,2.22,0.0,0.0,0.319,0.365,1.513,1.528,0.0,2.114,8.374,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,24.463,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,22.904,0.0,8.709,9.233,0.724,0.0,0.0,0.0,0.0,2122.9,2825.0,18.052,10.754,0.0,3.524,3.783,0.502,5.84,0.613,8.595,-6.603,0.0,0.0,0.0,6.558,-0.041,5.37,0.0,0.0,0.0,3.839,-6.765,-2.508,0.0,0.112,-0.273,-0.035,2.444,0.002,-1.643,8.266,0.0,0.0,0.0,-5.628,-0.038,-1.216,-2.073,0.0,0.0,1.265,5.681,2.001,1354.8,997.6,11399.5,2615.8,36000.0,24000.0,0.0,6.8,91.76,29361.0,8026.0,5506.0,0.0,575.0,6537.0,0.0,0.0,1949.0,2171.0,4597.0,15521.0,3263.0,5579.0,0.0,207.0,523.0,0.0,0.0,0.0,2010.0,619.0,3320.0,0.0,0.0,0.0,0.0
base-enclosure-infil-ach-house-pressure.xml,58.728,58.728,35.913,35.913,22.815,0.0,0.0,0.0,0.0,0.0,0.0,0.376,0.0,0.0,4.273,0.823,9.164,0.0,0.0,4.51,0.0,0.334,0.0,0.0,0.0,0.0,2.22,0.0,0.0,0.319,0.365,1.513,1.528,0.0,2.114,8.374,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,22.815,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,21.366,0.0,13.979,9.233,0.614,0.0,0.0,0.0,0.0,2115.3,3297.1,23.045,17.895,0.0,3.542,3.634,0.511,7.499,0.628,10.506,-12.551,0.0,0.0,0.0,8.272,-0.063,4.792,0.0,0.728,0.0,4.937,-8.907,-2.499,0.0,-0.043,-0.455,-0.051,2.706,-0.024,-1.917,11.732,0.0,0.0,0.0,-6.315,-0.059,-1.163,-3.064,-0.165,0.0,3.097,7.871,2.01,1354.8,997.6,11399.5,2615.8,36000.0,24000.0,0.0,6.8,91.76,31789.0,8583.0,7508.0,0.0,575.0,6409.0,0.0,0.0,1949.0,2171.0,4595.0,18786.0,5328.0,7037.0,0.0,207.0,265.0,0.0,0.0,0.0,2010.0,619.0,3320.0,0.0,0.0,0.0,0.0
diff --git a/workflow/tests/base_results/results_bills.csv b/workflow/tests/base_results/results_bills.csv
index 2ea79df1b0..be2906f700 100644
--- a/workflow/tests/base_results/results_bills.csv
+++ b/workflow/tests/base_results/results_bills.csv
@@ -124,6 +124,7 @@ base-enclosure-beds-2.xml,1566.38,144.0,1107.5,0.0,1251.5,144.0,170.88,314.88,0.
base-enclosure-beds-4.xml,1727.78,144.0,1283.19,0.0,1427.19,144.0,156.59,300.59,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
base-enclosure-beds-5.xml,1807.47,144.0,1369.91,0.0,1513.91,144.0,149.56,293.56,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
base-enclosure-ceilingtypes.xml,1777.08,144.0,1213.49,0.0,1357.49,144.0,275.59,419.59,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+base-enclosure-detailed-constructions.xml,1650.28,144.0,1197.95,0.0,1341.95,144.0,164.33,308.33,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
base-enclosure-floortypes.xml,1518.24,144.0,979.24,0.0,1123.24,144.0,251.0,395.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
base-enclosure-garage.xml,1615.24,144.0,1151.84,0.0,1295.84,144.0,175.4,319.4,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
base-enclosure-infil-ach-house-pressure.xml,1647.51,144.0,1195.93,0.0,1339.93,144.0,163.58,307.58,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
diff --git a/workflow/tests/hpxml_translator_test.rb b/workflow/tests/hpxml_translator_test.rb
index 5dfed85e9c..64618bbf50 100644
--- a/workflow/tests/hpxml_translator_test.rb
+++ b/workflow/tests/hpxml_translator_test.rb
@@ -694,10 +694,12 @@ def _verify_outputs(rundir, hpxml_path, results, hpxml)
assert_in_epsilon(hpxml_value, sql_value, 0.1)
# Solar absorptance
- hpxml_value = roof.solar_absorptance
- query = "SELECT AVG(Value) FROM TabularDataWithStrings WHERE ReportName='EnvelopeSummary' AND ReportForString='Entire Facility' AND TableName='Opaque Exterior' AND (RowName='#{roof_id}' OR RowName LIKE '#{roof_id}:%') AND ColumnName='Reflectance'"
- sql_value = 1.0 - sqlFile.execAndReturnFirstDouble(query).get
- assert_in_epsilon(hpxml_value, sql_value, 0.01)
+ if not roof.solar_absorptance.nil?
+ hpxml_value = roof.solar_absorptance
+ query = "SELECT AVG(Value) FROM TabularDataWithStrings WHERE ReportName='EnvelopeSummary' AND ReportForString='Entire Facility' AND TableName='Opaque Exterior' AND (RowName='#{roof_id}' OR RowName LIKE '#{roof_id}:%') AND ColumnName='Reflectance'"
+ sql_value = 1.0 - sqlFile.execAndReturnFirstDouble(query).get
+ assert_in_epsilon(hpxml_value, sql_value, 0.01)
+ end
# Tilt
hpxml_value = UnitConversions.convert(Math.atan(roof.pitch / 12.0), 'rad', 'deg')