mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-28 07:49:59 +00:00
Add target-unit pickers to the Qto panels; keep unit symbols live
This commit is contained in:
@@ -238,7 +238,6 @@ def import_attribute(
|
||||
elif data_type == "float":
|
||||
measure_class = attribute.type_of_attribute().declared_type().name()
|
||||
new.special_type = tool.Pset.get_special_type_for_measure_class(measure_class)
|
||||
new.unit_symbol = tool.Pset.get_unit_symbol_for_special_type(new.special_type, tool.Ifc.get())
|
||||
new.float_value = 0.0 if new.is_null else float(data[attribute.name()])
|
||||
elif data_type == "enum":
|
||||
attribute_type = attribute.type_of_attribute()
|
||||
|
||||
@@ -151,7 +151,7 @@ class CalculateSingleQuantity(bpy.types.Operator, tool.Ifc.Operator):
|
||||
ifc_file = tool.Ifc.get()
|
||||
with Profiler("Quantify function time:"):
|
||||
results = ifc5d.qto.quantify(ifc_file, elements, rules)
|
||||
ifc5d.qto.edit_qtos(ifc_file, results)
|
||||
ifc5d.qto.edit_qtos(ifc_file, results, target_units=tool.Qto.get_target_units(), rules=rules)
|
||||
|
||||
not_quantified_elements = elements - set(results.keys())
|
||||
not_quantified_message = tool.Qto.get_not_quantified_elements_message(not_quantified_elements)
|
||||
@@ -194,7 +194,7 @@ class PerformQuantityTakeOff(bpy.types.Operator, tool.Ifc.Operator):
|
||||
ifc_file = tool.Ifc.get()
|
||||
with Profiler("Quantify function time:"):
|
||||
results = ifc5d.qto.quantify(ifc_file, elements, rules)
|
||||
ifc5d.qto.edit_qtos(ifc_file, results)
|
||||
ifc5d.qto.edit_qtos(ifc_file, results, target_units=tool.Qto.get_target_units(), rules=rules)
|
||||
not_quantified_elements = elements - set(results.keys())
|
||||
return not_quantified_elements
|
||||
|
||||
|
||||
@@ -27,10 +27,28 @@ from bpy.props import (
|
||||
)
|
||||
from bpy.types import PropertyGroup
|
||||
|
||||
import bonsai.bim.prop
|
||||
import bonsai.tool as tool
|
||||
|
||||
CALCULATOR_FUNCTION_ENUM_ITEMS: list[Union[tuple[str, str, str], None]] = []
|
||||
|
||||
# Measure class (matching ifc5d.qto's Function.measure / SI2ProjectUnitConverter.project_units'
|
||||
# keys) -> (BIMQtoProperties field name, tool.Pset special_type, UI label).
|
||||
MEASURE_TO_TARGET_UNIT_FIELD: dict[str, tuple[str, str, str]] = {
|
||||
"IfcLengthMeasure": ("target_unit_length", "LENGTH", "Length"),
|
||||
"IfcAreaMeasure": ("target_unit_area", "AREA", "Area"),
|
||||
"IfcVolumeMeasure": ("target_unit_volume", "VOLUME", "Volume"),
|
||||
"IfcMassMeasure": ("target_unit_mass", "MASS", "Mass"),
|
||||
"IfcTimeMeasure": ("target_unit_time", "TIME", "Time"),
|
||||
}
|
||||
|
||||
|
||||
def _target_unit_items(special_type: str):
|
||||
def getter(self: "BIMQtoProperties", context: bpy.types.Context) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
return bonsai.bim.prop.get_unit_enum_items_for_special_type(special_type, tool.Ifc.get())
|
||||
|
||||
return getter
|
||||
|
||||
|
||||
def get_qto_rule(self: "BIMQtoProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
|
||||
results: list[tuple[str, str, str]] = []
|
||||
@@ -92,6 +110,11 @@ class BIMQtoProperties(PropertyGroup):
|
||||
),
|
||||
default=False,
|
||||
)
|
||||
target_unit_length: EnumProperty(items=_target_unit_items("LENGTH"), name="Length Unit")
|
||||
target_unit_area: EnumProperty(items=_target_unit_items("AREA"), name="Area Unit")
|
||||
target_unit_volume: EnumProperty(items=_target_unit_items("VOLUME"), name="Volume Unit")
|
||||
target_unit_mass: EnumProperty(items=_target_unit_items("MASS"), name="Mass Unit")
|
||||
target_unit_time: EnumProperty(items=_target_unit_items("TIME"), name="Time Unit")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
qto_rule: str
|
||||
@@ -101,3 +124,8 @@ class BIMQtoProperties(PropertyGroup):
|
||||
qto_name: str
|
||||
prop_name: str
|
||||
fallback: bool
|
||||
target_unit_length: str
|
||||
target_unit_area: str
|
||||
target_unit_volume: str
|
||||
target_unit_mass: str
|
||||
target_unit_time: str
|
||||
|
||||
@@ -17,9 +17,12 @@
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import ifc5d.qto
|
||||
|
||||
import bonsai.tool as tool
|
||||
from bonsai.bim.helper import prop_with_search
|
||||
from bonsai.bim.module.qto.data import QtoData
|
||||
from bonsai.bim.module.qto.prop import MEASURE_TO_TARGET_UNIT_FIELD
|
||||
|
||||
|
||||
class BIM_PT_qto(bpy.types.Panel):
|
||||
@@ -44,6 +47,14 @@ class BIM_PT_qto(bpy.types.Panel):
|
||||
row = layout.row()
|
||||
row.prop(props, "qto_rule", text="")
|
||||
row.prop(props, "fallback", text="", icon="RADIOBUT_ON" if props.fallback else "RADIOBUT_OFF")
|
||||
|
||||
box = layout.box()
|
||||
box.label(text="Target Units (optional, otherwise project default)")
|
||||
for field_name, _special_type, label in MEASURE_TO_TARGET_UNIT_FIELD.values():
|
||||
row = box.row(align=True)
|
||||
row.label(text=label)
|
||||
prop_with_search(row, props, field_name, text="")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.perform_quantity_take_off")
|
||||
|
||||
@@ -66,6 +77,15 @@ class BIM_PT_qto_manual(bpy.types.Panel):
|
||||
row = layout.row()
|
||||
row.prop(props, "calculator_function", text="Function")
|
||||
|
||||
calculator = ifc5d.qto.calculators.get(props.calculator)
|
||||
function = calculator.functions.get(props.calculator_function) if calculator else None
|
||||
target_unit_field = MEASURE_TO_TARGET_UNIT_FIELD.get(function.measure) if function else None
|
||||
if target_unit_field:
|
||||
field_name, _special_type, label = target_unit_field
|
||||
row = layout.row(align=True)
|
||||
row.label(text=f"{label} Unit")
|
||||
prop_with_search(row, props, field_name, text="")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "qto_name", text="")
|
||||
row.prop(props, "prop_name", text="")
|
||||
|
||||
@@ -123,32 +123,39 @@ def get_attribute_enum_values(prop: "Attribute", context: bpy.types.Context) ->
|
||||
return items
|
||||
|
||||
|
||||
def get_attribute_unit_enum_items(prop: "Attribute", context: bpy.types.Context) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
"""Items for `Attribute.unit_id_enum`: "(Project Default)" plus every candidate unit
|
||||
matching `prop.special_type`, filtered per-instance since candidates depend on the
|
||||
attribute's own measure type (unlike the globally-shared lists in `bonsai.bim.ui.EnumData`).
|
||||
def get_unit_enum_items_for_special_type(
|
||||
special_type: str, ifc_file: Union[ifcopenshell.file, None]
|
||||
) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
"""Items for a unit-override picker: "Default (<symbol>)" plus every candidate unit
|
||||
matching `special_type`, filtered per-caller since candidates depend on the measure type
|
||||
in question (unlike the globally-shared lists in `bonsai.bim.ui.EnumData`).
|
||||
"""
|
||||
ifc_file = tool.Ifc.get()
|
||||
if not ifc_file or not tool.Pset.is_measurable_special_type(prop.special_type):
|
||||
if not ifc_file or not tool.Pset.is_measurable_special_type(special_type):
|
||||
return [(cache_string("0"), cache_string("Default"), "")]
|
||||
|
||||
default_symbol = tool.Pset.get_unit_symbol_for_special_type(prop.special_type, ifc_file)
|
||||
default_symbol = tool.Pset.get_unit_symbol_for_special_type(special_type, ifc_file)
|
||||
items: list[tuple[str, str, str]] = [
|
||||
(cache_string("0"), cache_string(f"Default ({default_symbol})" if default_symbol else "Default"), "")
|
||||
]
|
||||
seen_ids = {0}
|
||||
for unit in tool.Pset.get_candidate_units_for_special_type(prop.special_type, ifc_file):
|
||||
for unit in tool.Pset.get_candidate_units_for_special_type(special_type, ifc_file):
|
||||
name = getattr(unit, "Name", None) or unit.is_a()
|
||||
symbol = ifcopenshell.util.unit.get_unit_symbol(unit)
|
||||
label = f"{name} ({symbol})" if symbol else name
|
||||
items.append((cache_string(str(unit.id())), cache_string(label), ""))
|
||||
seen_ids.add(unit.id())
|
||||
return items
|
||||
|
||||
# Defensive: real-world files sometimes carry a Unit that doesn't cleanly match our
|
||||
# candidate-matching logic (e.g. a mismatched UnitType). Always keep the attribute's own
|
||||
# current override selectable/representable, however unusual, so setting unit_id_enum to
|
||||
# match an already-seeded unit_id can never raise "enum not found".
|
||||
if prop.unit_id and prop.unit_id not in seen_ids:
|
||||
|
||||
def get_attribute_unit_enum_items(prop: "Attribute", context: bpy.types.Context) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
"""Items for `Attribute.unit_id_enum`. Wraps `get_unit_enum_items_for_special_type` with a
|
||||
defensive addition: real-world files sometimes carry a Unit that doesn't cleanly match our
|
||||
candidate-matching logic (e.g. a mismatched UnitType). Always keep the attribute's own
|
||||
current override selectable/representable, however unusual, so setting unit_id_enum to
|
||||
match an already-seeded unit_id can never raise "enum not found".
|
||||
"""
|
||||
ifc_file = tool.Ifc.get()
|
||||
items = get_unit_enum_items_for_special_type(prop.special_type, ifc_file)
|
||||
|
||||
if prop.unit_id and prop.unit_id not in {int(i[0]) for i in items}:
|
||||
own_unit = ifc_file.by_id(prop.unit_id)
|
||||
name = getattr(own_unit, "Name", None) or own_unit.is_a()
|
||||
symbol = ifcopenshell.util.unit.get_unit_symbol(own_unit)
|
||||
@@ -302,6 +309,20 @@ def get_display_name(self: "Attribute") -> str:
|
||||
return f"{name}, {self.unit_symbol}"
|
||||
|
||||
|
||||
def get_unit_symbol(self: "Attribute") -> str:
|
||||
"""The symbol for whatever unit the value is currently expressed in: this property's own
|
||||
override (`unit_id`) if set, else the project default for `special_type`. Computed fresh on
|
||||
every access (rather than cached at import time) so it stays correct immediately after the
|
||||
unit picker changes `unit_id`, and after the project's own default units are edited.
|
||||
"""
|
||||
if not tool.Pset.is_measurable_special_type(self.special_type):
|
||||
return ""
|
||||
if not (ifc_file := tool.Ifc.get()):
|
||||
return ""
|
||||
unit = tool.Pset.resolve_effective_unit(self.special_type, self.unit_id, ifc_file)
|
||||
return ifcopenshell.util.unit.get_unit_symbol(unit) if unit else ""
|
||||
|
||||
|
||||
AttributeDataType = Literal["string", "integer", "float", "boolean", "enum", "file", "list[string]"]
|
||||
# Either "", "DATE", "DATETIME", "LOGICAL", "URI", "DURATION", or an
|
||||
# IfcUnitEnum/IfcDerivedUnitEnum value with the "UNIT" suffix stripped (e.g.
|
||||
@@ -359,7 +380,7 @@ class Attribute(PropertyGroup):
|
||||
value_max: FloatProperty(description="This is used to validate int_value and float_value")
|
||||
value_max_constraint: BoolProperty(default=False, description="True if the numerical value has an upper bound")
|
||||
special_type: StringProperty(name="Special Value Type", default="")
|
||||
unit_symbol: StringProperty(name="Unit Symbol", default="")
|
||||
unit_symbol: StringProperty(name="Unit Symbol", get=get_unit_symbol)
|
||||
unit_id: IntProperty(name="Unit Override", default=0)
|
||||
"""STEP id of this property/quantity's own Unit override. 0 means "use the project default"."""
|
||||
unit_id_enum: EnumProperty(items=get_attribute_unit_enum_items, name="Unit", update=update_attribute_unit_id)
|
||||
|
||||
@@ -386,12 +386,12 @@ class Pset(bonsai.core.tool.Pset):
|
||||
metadata.is_null = value is None
|
||||
metadata.is_optional = True
|
||||
metadata.special_type = cls.get_special_type_for_prop(prop)
|
||||
metadata.unit_symbol = cls.get_unit_symbol_for_prop(prop, tool.Ifc.get())
|
||||
# The prop's OWN Unit override only, not the resolved project-default fallback
|
||||
# get_unit_symbol_for_prop() above already accounted for. Some real-world files
|
||||
# (e.g. certain exporters) set Unit on properties that aren't actually measures --
|
||||
# ignore it there, since we only ever treat Unit as meaningful for measurable
|
||||
# special_types (matching the UI picker's own gating).
|
||||
# The prop's OWN Unit override only -- metadata.unit_symbol is computed fresh from
|
||||
# special_type/unit_id on every access (see Attribute.get_unit_symbol), so it
|
||||
# already accounts for the project-default fallback once unit_id is set below.
|
||||
# Some real-world files (e.g. certain exporters) set Unit on properties that
|
||||
# aren't actually measures -- ignore it there, since we only ever treat Unit as
|
||||
# meaningful for measurable special_types (matching the UI picker's own gating).
|
||||
own_unit = getattr(prop, "Unit", None) if cls.is_measurable_special_type(metadata.special_type) else None
|
||||
metadata.unit_id = own_unit.id() if own_unit else 0
|
||||
metadata.unit_id_enum = str(metadata.unit_id)
|
||||
@@ -472,7 +472,6 @@ class Pset(bonsai.core.tool.Pset):
|
||||
metadata.is_optional = True
|
||||
metadata.data_type = cls.get_prop_template_primitive_type(prop_template)
|
||||
metadata.special_type = cls.get_special_type_for_prop(prop_template)
|
||||
metadata.unit_symbol = cls.get_unit_symbol_for_special_type(metadata.special_type, tool.Ifc.get())
|
||||
|
||||
if metadata.data_type == "string":
|
||||
metadata.string_value = "" if metadata.is_null else str(data[prop_template.Name])
|
||||
@@ -585,7 +584,6 @@ class Pset(bonsai.core.tool.Pset):
|
||||
metadata.is_null = value is None
|
||||
metadata.is_optional = True
|
||||
metadata.special_type = special_type
|
||||
metadata.unit_symbol = cls.get_unit_symbol_for_special_type(special_type, tool.Ifc.get())
|
||||
metadata.set_value(metadata.get_value_default() if metadata.is_null else value)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -176,6 +176,19 @@ class Qto(bonsai.core.tool.Qto):
|
||||
is_ifc4x3 = ifc_file.schema == "IFC4X3"
|
||||
return {rule_id: rule for rule_id, rule in ifc5d.qto.rules.items() if rule_id.startswith("IFC4X3") == is_ifc4x3}
|
||||
|
||||
@classmethod
|
||||
def get_target_units(cls) -> dict[str, ifcopenshell.entity_instance]:
|
||||
from bonsai.bim.module.qto.prop import MEASURE_TO_TARGET_UNIT_FIELD
|
||||
|
||||
props = cls.get_qto_props()
|
||||
ifc_file = tool.Ifc.get()
|
||||
target_units: dict[str, ifcopenshell.entity_instance] = {}
|
||||
for measure_class, (field_name, _special_type, _label) in MEASURE_TO_TARGET_UNIT_FIELD.items():
|
||||
unit_id = int(tool.Blender.get_enum_safe(props, field_name) or "0")
|
||||
if unit_id:
|
||||
target_units[measure_class] = ifc_file.by_id(unit_id)
|
||||
return target_units
|
||||
|
||||
@classmethod
|
||||
def get_not_quantified_elements_message(cls, not_quantified_elements: set[ifcopenshell.entity_instance]) -> str:
|
||||
not_quantified_message = ""
|
||||
|
||||
@@ -131,6 +131,71 @@ class TestGetAttributeUnitEnumItems(NewFile):
|
||||
assert [i[0] for i in items] == ["0"]
|
||||
|
||||
|
||||
class TestGetUnitEnumItemsForSpecialType(NewFile):
|
||||
def test_matches_the_attribute_wrapper_output(self):
|
||||
# Regression test for extracting get_unit_enum_items_for_special_type out of
|
||||
# get_attribute_unit_enum_items: the wrapper must still produce identical items for
|
||||
# the plain (no own-unit-fallback-needed) case.
|
||||
ifc = ifcopenshell.file()
|
||||
tool.Ifc.set(ifc)
|
||||
ifcopenshell.api.root.create_entity(ifc, ifc_class="IfcProject")
|
||||
length_mm = ifcopenshell.api.unit.add_si_unit(ifc, unit_type="LENGTHUNIT", prefix="MILLI")
|
||||
ifcopenshell.api.unit.assign_unit(ifc, units=[length_mm])
|
||||
ifcopenshell.api.unit.add_si_unit(ifc, unit_type="LENGTHUNIT")
|
||||
|
||||
element = ifc.createIfcWall()
|
||||
prop = ifc.createIfcPropertySingleValue(Name="Foo", NominalValue=ifc.createIfcLengthMeasure(2.5))
|
||||
metadata = import_single_property(ifc, element, prop)
|
||||
|
||||
direct_items = bonsai.bim.prop.get_unit_enum_items_for_special_type(metadata.special_type, ifc)
|
||||
wrapper_items = bonsai.bim.prop.get_attribute_unit_enum_items(metadata, bpy.context)
|
||||
assert direct_items == wrapper_items
|
||||
|
||||
def test_returns_just_default_when_ifc_file_is_none(self):
|
||||
assert bonsai.bim.prop.get_unit_enum_items_for_special_type("LENGTH", None) == [("0", "Default", "")]
|
||||
|
||||
|
||||
class TestUnitSymbolWithAreaVolumeDerivedFromLength(NewFile):
|
||||
"""Regression test: AREAUNIT/VOLUMEUNIT have no IfcDerivedUnitEnum member, so a project
|
||||
whose area/volume default is an IfcDerivedUnit rather than a literal-UnitType-matching
|
||||
IfcSIUnit/IfcConversionBasedUnit has no literal UnitType to match on.
|
||||
ifcopenshell.util.unit.get_project_unit() used to only match by literal UnitType, so the
|
||||
read-only unit symbol and the edit-mode "Default (<symbol>)" picker entry both silently
|
||||
fell back to no symbol at all in that case.
|
||||
"""
|
||||
|
||||
def setup_project_with_derived_area_and_volume(self, ifc):
|
||||
ifcopenshell.api.root.create_entity(ifc, ifc_class="IfcProject")
|
||||
length = ifcopenshell.api.unit.add_si_unit(ifc, unit_type="LENGTHUNIT")
|
||||
area = ifcopenshell.api.unit.add_derived_unit(ifc, "USERDEFINED", "area-ish", {length: 2})
|
||||
volume = ifcopenshell.api.unit.add_derived_unit(ifc, "USERDEFINED", "volume-ish", {length: 3})
|
||||
ifcopenshell.api.unit.assign_unit(ifc, units=[length, area, volume])
|
||||
|
||||
def test_default_picker_entry_shows_the_resolved_symbol(self):
|
||||
ifc = ifcopenshell.file()
|
||||
tool.Ifc.set(ifc)
|
||||
self.setup_project_with_derived_area_and_volume(ifc)
|
||||
|
||||
element = ifc.createIfcWall()
|
||||
prop = ifc.createIfcPropertySingleValue(Name="Foo", NominalValue=ifc.createIfcAreaMeasure(5.0))
|
||||
metadata = import_single_property(ifc, element, prop)
|
||||
|
||||
items = bonsai.bim.prop.get_attribute_unit_enum_items(metadata, bpy.context)
|
||||
assert items[0][1] == "Default (m2)"
|
||||
|
||||
def test_read_only_display_resolves_the_symbol(self):
|
||||
ifc = ifcopenshell.file()
|
||||
tool.Ifc.set(ifc)
|
||||
self.setup_project_with_derived_area_and_volume(ifc)
|
||||
|
||||
element = ifc.createIfcWall()
|
||||
prop = ifc.createIfcPropertySingleValue(Name="Foo", NominalValue=ifc.createIfcVolumeMeasure(5.0))
|
||||
metadata = import_single_property(ifc, element, prop)
|
||||
|
||||
assert metadata.unit_symbol == "m3"
|
||||
assert metadata.display_name == "Foo, m3"
|
||||
|
||||
|
||||
class TestUpdateAttributeUnitId(NewFile):
|
||||
def test_syncs_unit_id_and_converts_float_value_when_unit_id_enum_changes(self):
|
||||
ifc = ifcopenshell.file()
|
||||
@@ -145,11 +210,42 @@ class TestUpdateAttributeUnitId(NewFile):
|
||||
metadata = import_single_property(ifc, element, prop)
|
||||
assert metadata.unit_id == 0
|
||||
assert metadata.float_value == 2500.0
|
||||
assert metadata.unit_symbol == "mm"
|
||||
assert metadata.display_name == "Foo, mm"
|
||||
|
||||
metadata.unit_id_enum = str(length_m.id())
|
||||
|
||||
assert metadata.unit_id == length_m.id()
|
||||
assert metadata.float_value == pytest.approx(2.5) # converted, not just relabeled
|
||||
# Regression test: unit_symbol/display_name used to be a snapshot taken once at import
|
||||
# time, so picking a different unit converted the value but left the label showing the
|
||||
# old unit's symbol.
|
||||
assert metadata.unit_symbol == "m"
|
||||
assert metadata.display_name == "Foo, m"
|
||||
|
||||
|
||||
class TestUnitSymbolReflectsLiveProjectState(NewFile):
|
||||
def test_symbol_updates_after_a_project_default_unit_is_assigned_later(self):
|
||||
# Regression test: unit_symbol used to be a snapshot computed once at import time, so a
|
||||
# property/quantity imported before its measure type had a project default unit assigned
|
||||
# kept showing no symbol even after one was added, unless the panel was closed and
|
||||
# reopened (re-triggering import). It's now computed fresh on every access.
|
||||
ifc = ifcopenshell.file()
|
||||
tool.Ifc.set(ifc)
|
||||
ifcopenshell.api.root.create_entity(ifc, ifc_class="IfcProject")
|
||||
# No AREAUNIT assigned yet.
|
||||
|
||||
element = ifc.createIfcWall()
|
||||
prop = ifc.createIfcPropertySingleValue(Name="Foo", NominalValue=ifc.createIfcAreaMeasure(5.0))
|
||||
metadata = import_single_property(ifc, element, prop)
|
||||
assert metadata.unit_symbol == ""
|
||||
assert metadata.display_name == "Foo"
|
||||
|
||||
area = ifcopenshell.api.unit.add_si_unit(ifc, unit_type="AREAUNIT")
|
||||
ifcopenshell.api.unit.assign_unit(ifc, units=[area])
|
||||
|
||||
assert metadata.unit_symbol == "m2"
|
||||
assert metadata.display_name == "Foo, m2"
|
||||
|
||||
|
||||
class TestImportPsetFromExistingWithAGenericNumericValueAndAnExplicitUnit(NewFile):
|
||||
|
||||
@@ -181,6 +181,42 @@ class TestGetCalculatedObjectQuantities(test.bim.bootstrap.NewFile):
|
||||
assert quantities["NetVolume"] == 282.517
|
||||
|
||||
|
||||
class TestGetTargetUnits(test.bim.bootstrap.NewFile):
|
||||
def setup_file(self):
|
||||
ifc = ifcopenshell.file()
|
||||
tool.Ifc.set(ifc)
|
||||
ifcopenshell.api.root.create_entity(ifc, ifc_class="IfcProject", name="Test")
|
||||
return ifc
|
||||
|
||||
def test_default_scene_state_returns_nothing(self):
|
||||
self.setup_file()
|
||||
assert subject.get_target_units() == {}
|
||||
|
||||
def test_setting_a_field_maps_it_to_its_measure_class(self):
|
||||
ifc = self.setup_file()
|
||||
metre = ifc.createIfcSIUnit(None, "LENGTHUNIT", None, "METRE")
|
||||
millimetre = ifc.createIfcSIUnit(None, "LENGTHUNIT", "MILLI", "METRE")
|
||||
ifcopenshell.api.unit.assign_unit(ifc, units=[metre])
|
||||
|
||||
props = tool.Qto.get_qto_props()
|
||||
props.target_unit_length = str(millimetre.id())
|
||||
|
||||
assert subject.get_target_units() == {"IfcLengthMeasure": millimetre}
|
||||
|
||||
def test_untouched_fields_are_excluded(self):
|
||||
ifc = self.setup_file()
|
||||
metre = ifc.createIfcSIUnit(None, "LENGTHUNIT", None, "METRE")
|
||||
millimetre = ifc.createIfcSIUnit(None, "LENGTHUNIT", "MILLI", "METRE")
|
||||
gram = ifc.createIfcSIUnit(None, "MASSUNIT", None, "GRAM")
|
||||
ifcopenshell.api.unit.assign_unit(ifc, units=[metre, gram])
|
||||
|
||||
props = tool.Qto.get_qto_props()
|
||||
props.target_unit_length = str(millimetre.id())
|
||||
props.target_unit_mass = "0" # explicitly left at "Default"
|
||||
|
||||
assert subject.get_target_units() == {"IfcLengthMeasure": millimetre}
|
||||
|
||||
|
||||
class TestGetBaseQto(test.bim.bootstrap.NewFile):
|
||||
def test_run(self):
|
||||
ifc = ifcopenshell.file()
|
||||
|
||||
Reference in New Issue
Block a user