Merge pull request #7077 from falken10vdl/Add-Mass-and-Time-Unit-to-New-Project-Wizard-panel

Add mass and time unit to new project wizard panel
This commit is contained in:
falken10vdl
2025-12-16 08:46:46 +01:00
committed by GitHub
13 changed files with 419 additions and 26 deletions
@@ -119,6 +119,8 @@ class NewProject(bpy.types.Operator):
bpy.context.scene.unit_settings.length_unit = "MILLIMETERS" bpy.context.scene.unit_settings.length_unit = "MILLIMETERS"
bim_props.area_unit = "SQUARE_METRE" bim_props.area_unit = "SQUARE_METRE"
bim_props.volume_unit = "CUBIC_METRE" bim_props.volume_unit = "CUBIC_METRE"
bim_props.mass_unit = "KILOGRAM"
bim_props.time_unit = "SECOND"
pprops.template_file = "IFC4 Demo Template.ifc" pprops.template_file = "IFC4 Demo Template.ifc"
if self.preset != "wizard": if self.preset != "wizard":
@@ -356,8 +356,23 @@ class BIM_PT_new_project_wizard(Panel):
row.prop(props, "area_unit", text="Area Unit") row.prop(props, "area_unit", text="Area Unit")
row = self.layout.row() row = self.layout.row()
row.prop(props, "volume_unit", text="Volume Unit") row.prop(props, "volume_unit", text="Volume Unit")
row = self.layout.row()
prop_with_search(self.layout, pprops, "template_file", text="Template") prop_with_search(self.layout, pprops, "template_file", text="Template")
if tool.Blender.get_addon_preferences().mass_time_units_in_wizard:
header, body = self.layout.panel("Mass and Time Units", default_closed=True)
if header:
header.label(text="Mass and Time Units")
if body:
label = "Add Mass and Time Units" if not props.add_mass_time_units else "Remove Mass and Time Units"
body.prop(props, "add_mass_time_units", toggle=True, text=label)
if props.add_mass_time_units:
row = body.row()
row.prop(props, "mass_unit", text="Mass Unit")
row = body.row()
row.prop(props, "time_unit", text="Time Unit")
self.layout.use_property_split = True
row = self.layout.row() row = self.layout.row()
row.operator("bim.create_project") row.operator("bim.create_project")
+28
View File
@@ -586,7 +586,33 @@ class BIMProperties(PropertyGroup):
], ],
name="IFC Volume Unit", name="IFC Volume Unit",
) )
add_mass_time_units: bpy.props.BoolProperty(
name="Add Mass and Time Units",
description="Enable to define mass and time units for the project",
default=False,
)
mass_unit: EnumProperty(
items=[
("KILOGRAM", "Kilogram", "Kilograms"),
("GRAM", "Gram", "Grams"),
("POUND", "Pound", "Pounds"),
("OUNCE", "Ounce", "Ounces"),
("TONNE", "Tonne", "Metric Tons"),
],
name="Mass Unit",
default="KILOGRAM",
)
time_unit: EnumProperty(
items=[
("SECOND", "Second", "Seconds"),
("MINUTE", "Minute", "Minutes"),
("HOUR", "Hour", "Hours"),
("DAY", "Day", "Days"),
],
name="Time Unit",
default="HOUR",
)
if TYPE_CHECKING: if TYPE_CHECKING:
is_dirty: bool is_dirty: bool
schema_dir: str schema_dir: str
@@ -599,6 +625,8 @@ class BIMProperties(PropertyGroup):
section_line_decorator_width: float section_line_decorator_width: float
area_unit: str area_unit: str
volume_unit: str volume_unit: str
mass_unit: str
time_unit: str
class IfcParameter(PropertyGroup): class IfcParameter(PropertyGroup):
+8
View File
@@ -684,6 +684,12 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
default=False, default=False,
) )
mass_time_units_in_wizard: BoolProperty(
name="Mass and time units in project wizard",
description="Show mass and time units section in the new project wizard panel",
default=False,
)
if TYPE_CHECKING: if TYPE_CHECKING:
svg2pdf_command: str svg2pdf_command: str
svg2dxf_command: str svg2dxf_command: str
@@ -720,6 +726,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
doc: DocPreferences doc: DocPreferences
default_parameters: DefaultParameters default_parameters: DefaultParameters
container_hide_show_isolate: bool container_hide_show_isolate: bool
mass_time_units_in_wizard: bool
def draw(self, context: bpy.types.Context) -> None: def draw(self, context: bpy.types.Context) -> None:
layout = self.layout layout = self.layout
@@ -901,6 +908,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
def draw_extras_settings(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None: def draw_extras_settings(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
layout.prop(self, "container_hide_show_isolate") layout.prop(self, "container_hide_show_isolate")
layout.prop(self, "mass_time_units_in_wizard")
# Scene panel groups # Scene panel groups
+1 -1
View File
@@ -1123,7 +1123,7 @@ class Unit:
def set_active_unit(cls, unit): pass def set_active_unit(cls, unit): pass
def get_project_currency_unit(cls): pass def get_project_currency_unit(cls): pass
def get_currency_name(cls): pass def get_currency_name(cls): pass
def add_mass_and_time_units(cls): pass
@interface @interface
class Voider: class Voider:
+32 -9
View File
@@ -28,20 +28,43 @@ if TYPE_CHECKING:
def assign_scene_units(ifc: type[tool.Ifc], unit: type[tool.Unit]) -> None: def assign_scene_units(ifc: type[tool.Ifc], unit: type[tool.Unit]) -> None:
if unit.is_scene_unit_metric(): if unit.is_scene_unit_metric():
prefix = unit.get_scene_unit_si_prefix("LENGTHUNIT") lengthunit = ifc.run("unit.add_si_unit", unit_type="LENGTHUNIT", prefix=unit.get_scene_unit_si_prefix("LENGTHUNIT"))
lengthunit = ifc.run("unit.add_si_unit", unit_type="LENGTHUNIT", prefix=prefix) areaunit = ifc.run("unit.add_si_unit", unit_type="AREAUNIT", prefix=unit.get_scene_unit_si_prefix("AREAUNIT"))
prefix = unit.get_scene_unit_si_prefix("AREAUNIT") volumeunit = ifc.run("unit.add_si_unit", unit_type="VOLUMEUNIT", prefix=unit.get_scene_unit_si_prefix("VOLUMEUNIT"))
areaunit = ifc.run("unit.add_si_unit", unit_type="AREAUNIT", prefix=prefix) planeangleunit = ifc.run("unit.add_conversion_based_unit", name="degree")
prefix = unit.get_scene_unit_si_prefix("VOLUMEUNIT") units = [lengthunit, areaunit, volumeunit, planeangleunit]
volumeunit = ifc.run("unit.add_si_unit", unit_type="VOLUMEUNIT", prefix=prefix)
if unit.add_mass_and_time_units():
prefix = unit.get_scene_unit_si_prefix("MASSUNIT")
if prefix == "CONVERSION":
massunit = ifc.run("unit.add_conversion_based_unit", name=unit.get_scene_unit_name("MASSUNIT").lower())
else:
massunit = ifc.run("unit.add_si_unit", unit_type="MASSUNIT", prefix=prefix)
prefix = unit.get_scene_unit_si_prefix("TIMEUNIT")
if prefix == "CONVERSION":
timeunit = ifc.run("unit.add_conversion_based_unit", name=unit.get_scene_unit_name("TIMEUNIT").lower())
else:
timeunit = ifc.run("unit.add_si_unit", unit_type="TIMEUNIT", prefix=prefix)
units += [massunit, timeunit]
else: else:
lengthunit = ifc.run("unit.add_conversion_based_unit", name=unit.get_scene_unit_name("LENGTHUNIT")) lengthunit = ifc.run("unit.add_conversion_based_unit", name=unit.get_scene_unit_name("LENGTHUNIT"))
areaunit = ifc.run("unit.add_conversion_based_unit", name=unit.get_scene_unit_name("AREAUNIT")) areaunit = ifc.run("unit.add_conversion_based_unit", name=unit.get_scene_unit_name("AREAUNIT"))
volumeunit = ifc.run("unit.add_conversion_based_unit", name=unit.get_scene_unit_name("VOLUMEUNIT")) volumeunit = ifc.run("unit.add_conversion_based_unit", name=unit.get_scene_unit_name("VOLUMEUNIT"))
planeangleunit = ifc.run("unit.add_conversion_based_unit", name="degree")
units = [lengthunit, areaunit, volumeunit, planeangleunit]
planeangleunit = ifc.run("unit.add_conversion_based_unit", name="degree") if unit.add_mass_and_time_units():
massunit = ifc.run("unit.add_conversion_based_unit", name=unit.get_scene_unit_name("MASSUNIT").lower())
ifc.run("unit.assign_unit", units=[lengthunit, areaunit, volumeunit, planeangleunit]) time_unit_name = unit.get_scene_unit_name("TIMEUNIT")
if time_unit_name == "SECOND":
timeunit = ifc.run("unit.add_si_unit", unit_type="TIMEUNIT", prefix=None)
else:
timeunit = ifc.run("unit.add_conversion_based_unit", name=time_unit_name.lower())
units += [massunit, timeunit]
print("Add mass and time units:", unit.add_mass_and_time_units())
print("Assigning units:", units)
ifc.run("unit.assign_unit", units=units)
def assign_unit(ifc: type[tool.Ifc], unit_tool: type[tool.Unit], unit: ifcopenshell.entity_instance) -> None: def assign_unit(ifc: type[tool.Ifc], unit_tool: type[tool.Unit], unit: ifcopenshell.entity_instance) -> None:
+29 -1
View File
@@ -266,7 +266,7 @@ def parse_distance_string(input_string: str, use_project_unit: bool = True) -> t
class Unit(bonsai.core.tool.Unit): class Unit(bonsai.core.tool.Unit):
UNIT_TYPE = Literal["LENGTHUNIT", "AREAUNIT", "VOLUMEUNIT"] UNIT_TYPE = Literal["LENGTHUNIT", "AREAUNIT", "VOLUMEUNIT", "MASSUNIT", "TIMEUNIT"]
@staticmethod @staticmethod
def format_distance(meters: float, use_imperial: bool = None, **kwargs) -> str: def format_distance(meters: float, use_imperial: bool = None, **kwargs) -> str:
@@ -343,6 +343,10 @@ class Unit(bonsai.core.tool.Unit):
return bim_props.area_unit return bim_props.area_unit
elif unit_type == "VOLUMEUNIT": elif unit_type == "VOLUMEUNIT":
return bim_props.volume_unit return bim_props.volume_unit
elif unit_type == "MASSUNIT":
return bim_props.mass_unit.lower()
elif unit_type == "TIMEUNIT":
return bim_props.time_unit.lower()
else: else:
assert_never(unit_type) assert_never(unit_type)
@@ -359,6 +363,24 @@ class Unit(bonsai.core.tool.Unit):
unit = bim_props.area_unit unit = bim_props.area_unit
elif unit_type == "VOLUMEUNIT": elif unit_type == "VOLUMEUNIT":
unit = bim_props.volume_unit unit = bim_props.volume_unit
elif unit_type == "MASSUNIT":
unit = bim_props.mass_unit
if unit == "GRAM":
return None
elif unit == "KILOGRAM":
return "KILO"
elif unit == "TONNE":
return "MEGA"
elif unit in ["POUND", "OUNCE"]:
return "CONVERSION"
else:
return None
elif unit_type == "TIMEUNIT":
unit = bim_props.time_unit
if unit == "SECOND":
return None
else:
return "CONVERSION"
else: else:
assert_never(unit_type) assert_never(unit_type)
if "/" in unit: if "/" in unit:
@@ -477,3 +499,9 @@ class Unit(bonsai.core.tool.Unit):
elif ifc_class == "IfcMonetaryUnit": elif ifc_class == "IfcMonetaryUnit":
return "COPY_ID" return "COPY_ID"
return "MOD_MESHDEFORM" return "MOD_MESHDEFORM"
@classmethod
def add_mass_and_time_units(cls) -> bool:
"""Return True if the user wants to add mass and time units, False otherwise."""
bim_props = tool.Blender.get_bim_props()
return getattr(bim_props, "add_mass_time_units", False)
+92 -11
View File
@@ -26,38 +26,119 @@ class TestAssignSceneUnits:
unit.get_scene_unit_si_prefix("LENGTHUNIT").should_be_called().will_return("prefix") unit.get_scene_unit_si_prefix("LENGTHUNIT").should_be_called().will_return("prefix")
unit.get_scene_unit_si_prefix("AREAUNIT").should_be_called().will_return("prefix") unit.get_scene_unit_si_prefix("AREAUNIT").should_be_called().will_return("prefix")
unit.get_scene_unit_si_prefix("VOLUMEUNIT").should_be_called().will_return("prefix") unit.get_scene_unit_si_prefix("VOLUMEUNIT").should_be_called().will_return("prefix")
unit.add_mass_and_time_units().should_be_called().will_return(True)
unit.get_scene_unit_si_prefix("MASSUNIT").should_be_called().will_return("KILO")
unit.get_scene_unit_si_prefix("TIMEUNIT").should_be_called().will_return(None)
ifc.run("unit.add_si_unit", unit_type="LENGTHUNIT", prefix="prefix").should_be_called().will_return( ifc.run("unit.add_si_unit", unit_type="LENGTHUNIT", prefix="prefix").should_be_called().will_return(
"lengthunit" "lengthunit"
) )
ifc.run("unit.add_si_unit", unit_type="AREAUNIT", prefix="prefix").should_be_called().will_return("areaunit") ifc.run("unit.add_si_unit", unit_type="AREAUNIT", prefix="prefix").should_be_called().will_return("areaunit")
ifc.run("unit.add_si_unit", unit_type="VOLUMEUNIT", prefix="prefix").should_be_called().will_return( ifc.run("unit.add_si_unit", unit_type="VOLUMEUNIT", prefix="prefix").should_be_called().will_return(
"volumeunit" "volumeunit"
) )
ifc.run("unit.add_si_unit", unit_type="MASSUNIT", prefix="KILO").should_be_called().will_return("massunit")
ifc.run("unit.add_si_unit", unit_type="TIMEUNIT", prefix=None).should_be_called().will_return("timeunit")
ifc.run("unit.add_conversion_based_unit", name="degree").should_be_called().will_return("planeangleunit") ifc.run("unit.add_conversion_based_unit", name="degree").should_be_called().will_return("planeangleunit")
ifc.run(
"unit.assign_unit", units=["lengthunit", "areaunit", "volumeunit", "planeangleunit", "massunit", "timeunit"]
).should_be_called()
subject.assign_scene_units(ifc, unit)
def test_creating_and_assigning_metric_units_without_mass_and_time(self, ifc, unit):
unit.is_scene_unit_metric().should_be_called().will_return(True)
unit.get_scene_unit_si_prefix("LENGTHUNIT").should_be_called().will_return("CENTI")
unit.get_scene_unit_si_prefix("AREAUNIT").should_be_called().will_return("CENTI")
unit.get_scene_unit_si_prefix("VOLUMEUNIT").should_be_called().will_return("CENTI")
unit.add_mass_and_time_units().should_be_called().will_return(False)
ifc.run("unit.add_si_unit", unit_type="LENGTHUNIT", prefix="CENTI").should_be_called().will_return("lengthunit")
ifc.run("unit.add_si_unit", unit_type="AREAUNIT", prefix="CENTI").should_be_called().will_return("areaunit")
ifc.run("unit.add_si_unit", unit_type="VOLUMEUNIT", prefix="CENTI").should_be_called().will_return("volumeunit")
ifc.run("unit.add_conversion_based_unit", name="degree").should_be_called().will_return("planeangleunit")
ifc.run("unit.assign_unit", units=["lengthunit", "areaunit", "volumeunit", "planeangleunit"]).should_be_called() ifc.run("unit.assign_unit", units=["lengthunit", "areaunit", "volumeunit", "planeangleunit"]).should_be_called()
subject.assign_scene_units(ifc, unit) subject.assign_scene_units(ifc, unit)
def test_creating_and_assigning_imperial_units(self, ifc, unit): def test_creating_and_assigning_imperial_units(self, ifc, unit):
unit.is_scene_unit_metric().should_be_called().will_return(False) unit.is_scene_unit_metric().should_be_called().will_return(False)
unit.get_scene_unit_name("LENGTHUNIT").should_be_called().will_return("lengthname") unit.get_scene_unit_name("LENGTHUNIT").should_be_called().will_return("foot")
ifc.run("unit.add_conversion_based_unit", name="lengthname").should_be_called().will_return("lengthunit") unit.get_scene_unit_name("AREAUNIT").should_be_called().will_return("square foot")
unit.get_scene_unit_name("VOLUMEUNIT").should_be_called().will_return("cubic foot")
unit.get_scene_unit_name("AREAUNIT").should_be_called().will_return("areaname") unit.add_mass_and_time_units().should_be_called().will_return(True)
ifc.run("unit.add_conversion_based_unit", name="areaname").should_be_called().will_return("areaunit") unit.get_scene_unit_name("MASSUNIT").should_be_called().will_return("pound")
unit.get_scene_unit_name("TIMEUNIT").should_be_called().will_return("SECOND")
unit.get_scene_unit_name("VOLUMEUNIT").should_be_called().will_return("volumename")
ifc.run("unit.add_conversion_based_unit", name="volumename").should_be_called().will_return("volumeunit")
ifc.run("unit.add_conversion_based_unit", name="foot").should_be_called().will_return("lengthunit")
ifc.run("unit.add_conversion_based_unit", name="square foot").should_be_called().will_return("areaunit")
ifc.run("unit.add_conversion_based_unit", name="cubic foot").should_be_called().will_return("volumeunit")
ifc.run("unit.add_conversion_based_unit", name="pound").should_be_called().will_return("massunit")
ifc.run("unit.add_si_unit", unit_type="TIMEUNIT", prefix=None).should_be_called().will_return("timeunit")
ifc.run("unit.add_conversion_based_unit", name="degree").should_be_called().will_return("planeangleunit") ifc.run("unit.add_conversion_based_unit", name="degree").should_be_called().will_return("planeangleunit")
ifc.run(
"unit.assign_unit", units=["lengthunit", "areaunit", "volumeunit", "planeangleunit", "massunit", "timeunit"]
).should_be_called()
subject.assign_scene_units(ifc, unit)
def test_creating_and_assigning_imperial_units_without_mass_and_time(self, ifc, unit):
unit.is_scene_unit_metric().should_be_called().will_return(False)
unit.get_scene_unit_name("LENGTHUNIT").should_be_called().will_return("yard")
unit.get_scene_unit_name("AREAUNIT").should_be_called().will_return("square yard")
unit.get_scene_unit_name("VOLUMEUNIT").should_be_called().will_return("cubic yard")
unit.add_mass_and_time_units().should_be_called().will_return(False)
ifc.run("unit.add_conversion_based_unit", name="yard").should_be_called().will_return("lengthunit")
ifc.run("unit.add_conversion_based_unit", name="square yard").should_be_called().will_return("areaunit")
ifc.run("unit.add_conversion_based_unit", name="cubic yard").should_be_called().will_return("volumeunit")
ifc.run("unit.add_conversion_based_unit", name="degree").should_be_called().will_return("planeangleunit")
ifc.run("unit.assign_unit", units=["lengthunit", "areaunit", "volumeunit", "planeangleunit"]).should_be_called() ifc.run("unit.assign_unit", units=["lengthunit", "areaunit", "volumeunit", "planeangleunit"]).should_be_called()
subject.assign_scene_units(ifc, unit) subject.assign_scene_units(ifc, unit)
def test_creating_metric_units_with_conversion_based_mass_and_time(self, ifc, unit):
unit.is_scene_unit_metric().should_be_called().will_return(True)
unit.get_scene_unit_si_prefix("LENGTHUNIT").should_be_called().will_return("MILLI")
unit.get_scene_unit_si_prefix("AREAUNIT").should_be_called().will_return(None)
unit.get_scene_unit_si_prefix("VOLUMEUNIT").should_be_called().will_return(None)
unit.add_mass_and_time_units().should_be_called().will_return(True)
unit.get_scene_unit_si_prefix("MASSUNIT").should_be_called().will_return("CONVERSION")
unit.get_scene_unit_name("MASSUNIT").should_be_called().will_return("tonne")
unit.get_scene_unit_si_prefix("TIMEUNIT").should_be_called().will_return("CONVERSION")
unit.get_scene_unit_name("TIMEUNIT").should_be_called().will_return("minute")
ifc.run("unit.add_si_unit", unit_type="LENGTHUNIT", prefix="MILLI").should_be_called().will_return("lengthunit")
ifc.run("unit.add_si_unit", unit_type="AREAUNIT", prefix=None).should_be_called().will_return("areaunit")
ifc.run("unit.add_si_unit", unit_type="VOLUMEUNIT", prefix=None).should_be_called().will_return("volumeunit")
ifc.run("unit.add_conversion_based_unit", name="tonne").should_be_called().will_return("massunit")
ifc.run("unit.add_conversion_based_unit", name="minute").should_be_called().will_return("timeunit")
ifc.run("unit.add_conversion_based_unit", name="degree").should_be_called().will_return("planeangleunit")
ifc.run(
"unit.assign_unit", units=["lengthunit", "areaunit", "volumeunit", "planeangleunit", "massunit", "timeunit"]
).should_be_called()
subject.assign_scene_units(ifc, unit)
def test_creating_imperial_units_with_conversion_based_units(self, ifc, unit):
unit.is_scene_unit_metric().should_be_called().will_return(False)
unit.get_scene_unit_name("LENGTHUNIT").should_be_called().will_return("inch")
unit.get_scene_unit_name("AREAUNIT").should_be_called().will_return("square inch")
unit.get_scene_unit_name("VOLUMEUNIT").should_be_called().will_return("cubic inch")
unit.add_mass_and_time_units().should_be_called().will_return(True)
unit.get_scene_unit_name("MASSUNIT").should_be_called().will_return("ounce")
unit.get_scene_unit_name("TIMEUNIT").should_be_called().will_return("hour")
ifc.run("unit.add_conversion_based_unit", name="inch").should_be_called().will_return("lengthunit")
ifc.run("unit.add_conversion_based_unit", name="square inch").should_be_called().will_return("areaunit")
ifc.run("unit.add_conversion_based_unit", name="cubic inch").should_be_called().will_return("volumeunit")
ifc.run("unit.add_conversion_based_unit", name="ounce").should_be_called().will_return("massunit")
ifc.run("unit.add_conversion_based_unit", name="hour").should_be_called().will_return("timeunit")
ifc.run("unit.add_conversion_based_unit", name="degree").should_be_called().will_return("planeangleunit")
ifc.run(
"unit.assign_unit", units=["lengthunit", "areaunit", "volumeunit", "planeangleunit", "massunit", "timeunit"]
).should_be_called()
subject.assign_scene_units(ifc, unit)
class TestAssignUnit: class TestAssignUnit:
def test_run(self, ifc, unit): def test_run(self, ifc, unit):
ifc.run("unit.assign_unit", units=["unit"]).should_be_called() ifc.run("unit.assign_unit", units=["unit"]).should_be_called()
+143
View File
@@ -135,6 +135,35 @@ class TestGetSceneUnitName(NewFile):
bpy.context.scene.unit_settings.system = "NONE" bpy.context.scene.unit_settings.system = "NONE"
assert subject.get_scene_unit_name("LENGTHUNIT") == "foot" assert subject.get_scene_unit_name("LENGTHUNIT") == "foot"
def test_getting_mass_unit_names(self):
"""Test getting mass unit names for different systems"""
assert bpy.context.scene
props = tool.Blender.get_bim_props()
props.mass_unit = "GRAM"
assert subject.get_scene_unit_name("MASSUNIT") == "gram"
props.mass_unit = "KILOGRAM"
assert subject.get_scene_unit_name("MASSUNIT") == "kilogram"
props.mass_unit = "POUND"
assert subject.get_scene_unit_name("MASSUNIT") == "pound"
props.mass_unit = "OUNCE"
assert subject.get_scene_unit_name("MASSUNIT") == "ounce"
props.mass_unit = "TONNE"
assert subject.get_scene_unit_name("MASSUNIT") == "tonne"
def test_getting_time_unit_names(self):
"""Test getting time unit names for different systems"""
assert bpy.context.scene
props = tool.Blender.get_bim_props()
props.time_unit = "SECOND"
assert subject.get_scene_unit_name("TIMEUNIT") == "second"
props.time_unit = "MINUTE"
assert subject.get_scene_unit_name("TIMEUNIT") == "minute"
props.time_unit = "HOUR"
assert subject.get_scene_unit_name("TIMEUNIT") == "hour"
props.time_unit = "DAY"
assert subject.get_scene_unit_name("TIMEUNIT") == "day"
class TestGetSceneUnitSIPrefix: class TestGetSceneUnitSIPrefix:
def test_run(self): def test_run(self):
@@ -162,6 +191,30 @@ class TestGetSceneUnitSIPrefix:
props.volume_unit = "MILLI/CUBIC_METRE" props.volume_unit = "MILLI/CUBIC_METRE"
assert subject.get_scene_unit_si_prefix("VOLUMEUNIT") == "MILLI" assert subject.get_scene_unit_si_prefix("VOLUMEUNIT") == "MILLI"
def test_mass_and_time_unit_prefixes(self):
assert bpy.context.scene
props = tool.Blender.get_bim_props()
props.mass_unit = "KILOGRAM"
assert subject.get_scene_unit_si_prefix("MASSUNIT") == "KILO"
props.mass_unit = "GRAM"
assert subject.get_scene_unit_si_prefix("MASSUNIT") is None
props.mass_unit = "POUND"
assert subject.get_scene_unit_si_prefix("MASSUNIT") == "CONVERSION"
props.mass_unit = "OUNCE"
assert subject.get_scene_unit_si_prefix("MASSUNIT") == "CONVERSION"
props.mass_unit = "TONNE"
assert subject.get_scene_unit_si_prefix("MASSUNIT") == "MEGA"
props.time_unit = "SECOND"
assert subject.get_scene_unit_si_prefix("TIMEUNIT") is None
props.time_unit = "MINUTE"
assert subject.get_scene_unit_si_prefix("TIMEUNIT") == "CONVERSION"
props.time_unit = "HOUR"
assert subject.get_scene_unit_si_prefix("TIMEUNIT") == "CONVERSION"
props.time_unit = "DAY"
assert subject.get_scene_unit_si_prefix("TIMEUNIT") == "CONVERSION"
class TestImportUnitAttributes(NewFile): class TestImportUnitAttributes(NewFile):
def test_importing_derived_units(self): def test_importing_derived_units(self):
@@ -298,6 +351,96 @@ class TestImportUnits(NewFile):
assert props.units[5].unit_type == unit6.UnitType assert props.units[5].unit_type == unit6.UnitType
assert props.units[5].ifc_class == unit6.is_a() assert props.units[5].ifc_class == unit6.is_a()
def test_importing_mass_and_time_units(self):
"""Test importing mass and time conversion based units"""
ifc = ifcopenshell.api.project.create_file()
tool.Ifc.set(ifc)
tonne_unit = ifcopenshell.api.unit.add_conversion_based_unit(ifc, name="tonne")
pound_unit = ifcopenshell.api.unit.add_conversion_based_unit(ifc, name="pound")
ounce_unit = ifcopenshell.api.unit.add_conversion_based_unit(ifc, name="ounce")
minute_unit = ifcopenshell.api.unit.add_conversion_based_unit(ifc, name="minute")
hour_unit = ifcopenshell.api.unit.add_conversion_based_unit(ifc, name="hour")
day_unit = ifcopenshell.api.unit.add_conversion_based_unit(ifc, name="day")
kg_unit = ifcopenshell.api.unit.add_si_unit(ifc, unit_type="MASSUNIT", prefix="KILO")
gram_unit = ifcopenshell.api.unit.add_si_unit(ifc, unit_type="MASSUNIT")
second_unit = ifcopenshell.api.unit.add_si_unit(ifc, unit_type="TIMEUNIT")
ifcopenshell.api.root.create_entity(ifc, ifc_class="IfcProject")
ifcopenshell.api.unit.assign_unit(ifc, units=[tonne_unit, minute_unit, kg_unit])
subject.import_units()
props = tool.Unit.get_unit_props()
assert len(props.units) == 15
unit_ids = [u.ifc_definition_id for u in props.units]
assert tonne_unit.id() in unit_ids
assert pound_unit.id() in unit_ids
assert ounce_unit.id() in unit_ids
assert minute_unit.id() in unit_ids
assert hour_unit.id() in unit_ids
assert day_unit.id() in unit_ids
assert kg_unit.id() in unit_ids
assert gram_unit.id() in unit_ids
assert second_unit.id() in unit_ids
tonne_prop = next(u for u in props.units if u.ifc_definition_id == tonne_unit.id())
assert tonne_prop.name == "tonne"
assert tonne_prop.unit_type == "MASSUNIT"
assert tonne_prop.is_assigned is True
assert tonne_prop.ifc_class == "IfcConversionBasedUnit"
pound_prop = next(u for u in props.units if u.ifc_definition_id == pound_unit.id())
assert pound_prop.name == "pound"
assert pound_prop.unit_type == "MASSUNIT"
assert pound_prop.is_assigned is False
assert pound_prop.ifc_class == "IfcConversionBasedUnit"
ounce_prop = next(u for u in props.units if u.ifc_definition_id == ounce_unit.id())
assert ounce_prop.name == "ounce"
assert ounce_prop.unit_type == "MASSUNIT"
assert ounce_prop.is_assigned is False
assert ounce_prop.ifc_class == "IfcConversionBasedUnit"
kg_prop = next(u for u in props.units if u.ifc_definition_id == kg_unit.id())
assert kg_prop.name == "KILOGRAM"
assert kg_prop.unit_type == "MASSUNIT"
assert kg_prop.is_assigned is True
assert kg_prop.ifc_class == "IfcSIUnit"
gram_prop = next(u for u in props.units if u.ifc_definition_id == gram_unit.id())
assert gram_prop.name == "GRAM"
assert gram_prop.unit_type == "MASSUNIT"
assert gram_prop.is_assigned is False
assert gram_prop.ifc_class == "IfcSIUnit"
minute_prop = next(u for u in props.units if u.ifc_definition_id == minute_unit.id())
assert minute_prop.name == "minute"
assert minute_prop.unit_type == "TIMEUNIT"
assert minute_prop.is_assigned is True
assert minute_prop.ifc_class == "IfcConversionBasedUnit"
hour_prop = next(u for u in props.units if u.ifc_definition_id == hour_unit.id())
assert hour_prop.name == "hour"
assert hour_prop.unit_type == "TIMEUNIT"
assert hour_prop.is_assigned is False
assert hour_prop.ifc_class == "IfcConversionBasedUnit"
day_prop = next(u for u in props.units if u.ifc_definition_id == day_unit.id())
assert day_prop.name == "day"
assert day_prop.unit_type == "TIMEUNIT"
assert day_prop.is_assigned is False
assert day_prop.ifc_class == "IfcConversionBasedUnit"
second_prop = next(u for u in props.units if u.ifc_definition_id == second_unit.id())
assert second_prop.name == "SECOND"
assert second_prop.unit_type == "TIMEUNIT"
assert second_prop.is_assigned is False
assert second_prop.ifc_class == "IfcSIUnit"
class TestIsSceneUnitMetric(NewFile): class TestIsSceneUnitMetric(NewFile):
def test_run(self): def test_run(self):
@@ -32,7 +32,7 @@ def add_conversion_based_unit(
function. You can choose from one of: inch, foot, yard, mile, square function. You can choose from one of: inch, foot, yard, mile, square
inch, square foot, square yard, acre, square mile, cubic inch, cubic inch, square foot, square yard, acre, square mile, cubic inch, cubic
foot, cubic yard, litre, fluid ounce UK, fluid ounce US, pint UK, pint foot, cubic yard, litre, fluid ounce UK, fluid ounce US, pint UK, pint
US, gallon UK, gallon US, degree, ounce, pound, ton UK, ton US, lbf, US, gallon UK, gallon US, degree, ounce, pound, ton UK, ton US, tonne, lbf,
kip, psi, ksi, minute, hour, day, btu, and fahrenheit. kip, psi, ksi, minute, hour, day, btu, and fahrenheit.
:param name: A converted name chosen from the list above. :param name: A converted name chosen from the list above.
@@ -61,7 +61,11 @@ def add_conversion_based_unit(
dimensions = ifcopenshell.util.unit.named_dimensions[unit_type] dimensions = ifcopenshell.util.unit.named_dimensions[unit_type]
exponents = file.createIfcDimensionalExponents(*dimensions) exponents = file.createIfcDimensionalExponents(*dimensions)
si_name = ifcopenshell.util.unit.si_type_names[unit_type] si_name = ifcopenshell.util.unit.si_type_names[unit_type]
si_unit = file.createIfcSIUnit(UnitType=unit_type, Name=si_name)
if unit_type == "MASSUNIT":
si_unit = file.createIfcSIUnit(UnitType=unit_type, Name=si_name, Prefix="KILO")
else:
si_unit = file.createIfcSIUnit(UnitType=unit_type, Name=si_name)
conversion_real = ifcopenshell.util.unit.si_conversions.get(name, 1) conversion_real = ifcopenshell.util.unit.si_conversions.get(name, 1)
value_component = file.create_entity("IfcReal", **{"wrappedValue": conversion_real}) value_component = file.create_entity("IfcReal", **{"wrappedValue": conversion_real})
@@ -55,8 +55,12 @@ def assign_unit(
length = ifcopenshell.api.unit.add_si_unit(model, unit_type="LENGTHUNIT", prefix="MILLI") length = ifcopenshell.api.unit.add_si_unit(model, unit_type="LENGTHUNIT", prefix="MILLI")
area = ifcopenshell.api.unit.add_si_unit(model, unit_type="AREAUNIT") area = ifcopenshell.api.unit.add_si_unit(model, unit_type="AREAUNIT")
# Make it our default units, if we are doing a metric building # Optionally, add mass and time units
ifcopenshell.api.unit.assign_unit(model, units=[length, area]) mass = ifcopenshell.api.unit.add_si_unit(model, unit_type="MASSUNIT", prefix="KILO")
time = ifcopenshell.api.unit.add_si_unit(model, unit_type="TIMEUNIT")
# Make these the default units for the project
ifcopenshell.api.unit.assign_unit(model, units=[length, area, mass, time])
# Alternatively, you may specify without any arguments to # Alternatively, you may specify without any arguments to
# automatically create millimeters, square meters, and cubic meters # automatically create millimeters, square meters, and cubic meters
@@ -139,6 +143,7 @@ class Usecase:
elif unit_type == "volume": elif unit_type == "volume":
dimensional_exponents = self.file.createIfcDimensionalExponents(3, 0, 0, 0, 0, 0, 0) dimensional_exponents = self.file.createIfcDimensionalExponents(3, 0, 0, 0, 0, 0, 0)
name_prefix = "cubic" name_prefix = "cubic"
si_unit = self.file.createIfcSIUnit( si_unit = self.file.createIfcSIUnit(
None, None,
"{}UNIT".format(unit_type.upper()), "{}UNIT".format(unit_type.upper()),
@@ -209,6 +209,7 @@ si_conversions = {
"pound": 0.454, "pound": 0.454,
"ton UK": 1016.0469088, "ton UK": 1016.0469088,
"ton US": 907.18474, "ton US": 907.18474,
"tonne": 1000.0,
"lbf": 4.4482216153, "lbf": 4.4482216153,
"kip": 4448.2216153, "kip": 4448.2216153,
"psi": 6894.7572932, "psi": 6894.7572932,
@@ -253,6 +254,7 @@ imperial_types = {
"pound": "MASSUNIT", "pound": "MASSUNIT",
"ton UK": "MASSUNIT", "ton UK": "MASSUNIT",
"ton US": "MASSUNIT", "ton US": "MASSUNIT",
"tonne": "MASSUNIT",
"lbf": "FORCEUNIT", "lbf": "FORCEUNIT",
"kip": "FORCEUNIT", "kip": "FORCEUNIT",
"psi": "PRESSUREUNIT", "psi": "PRESSUREUNIT",
@@ -323,6 +325,7 @@ unit_symbols = {
"pound": "lb", "pound": "lb",
"ton UK": "ton", "ton UK": "ton",
"ton US": "ton", "ton US": "ton",
"tonne": "t",
"lbf": "lbf", "lbf": "lbf",
"kip": "kip", "kip": "kip",
"psi": "psi", "psi": "psi",
@@ -40,6 +40,54 @@ class TestAddConversionBasedUnitIFC2X3(test.bootstrap.IFC2X3):
assert si_unit.Prefix is None assert si_unit.Prefix is None
assert si_unit.Name == "METRE" assert si_unit.Name == "METRE"
def test_adding_mass_units_creates_proper_massunit(self):
mass_units = [
("tonne", 1000.0),
("pound", 0.454),
("ounce", 0.02835),
("ton UK", 1016.0469088),
("ton US", 907.18474),
]
for name, expected_conversion in mass_units:
unit = ifcopenshell.api.unit.add_conversion_based_unit(self.file, name=name)
assert unit.is_a("IfcConversionBasedUnit")
assert unit.UnitType == "MASSUNIT"
assert unit.Name == name
actual_conversion = unit.ConversionFactor.ValueComponent.wrappedValue
assert actual_conversion == expected_conversion
target_unit = unit.ConversionFactor.UnitComponent
assert target_unit.is_a("IfcSIUnit")
assert target_unit.UnitType == "MASSUNIT"
assert target_unit.Name == "GRAM"
assert target_unit.Prefix == "KILO"
def test_adding_time_units_creates_proper_timeunit(self):
time_units = [
("minute", 60),
("hour", 3600),
("day", 86400),
]
for name, expected_conversion in time_units:
unit = ifcopenshell.api.unit.add_conversion_based_unit(self.file, name=name)
assert unit.is_a("IfcConversionBasedUnit")
assert unit.UnitType == "TIMEUNIT"
assert unit.Name == name
actual_conversion = unit.ConversionFactor.ValueComponent.wrappedValue
assert actual_conversion == expected_conversion
target_unit = unit.ConversionFactor.UnitComponent
assert target_unit.is_a("IfcSIUnit")
assert target_unit.UnitType == "TIMEUNIT"
assert target_unit.Name == "SECOND"
assert target_unit.Prefix is None
class TestAddConversionBasedUnitIFC4(test.bootstrap.IFC4, TestAddConversionBasedUnitIFC2X3): class TestAddConversionBasedUnitIFC4(test.bootstrap.IFC4, TestAddConversionBasedUnitIFC2X3):
def test_adding_a_unit_with_offset(self): def test_adding_a_unit_with_offset(self):
@@ -61,3 +109,8 @@ class TestAddConversionBasedUnitIFC4(test.bootstrap.IFC4, TestAddConversionBased
assert si_unit.Prefix is None assert si_unit.Prefix is None
assert si_unit.Name == "KELVIN" assert si_unit.Name == "KELVIN"
assert unit.ConversionOffset == -459.67 assert unit.ConversionOffset == -459.67
def test_unknown_units_fall_back_to_userdefined(self):
unknown_unit = ifcopenshell.api.unit.add_conversion_based_unit(self.file, name="unknown_unit")
assert unknown_unit.UnitType == "USERDEFINED"
assert unknown_unit.Name == "unknown_unit"