2024-08-13 15:47:27 +10:00
|
|
|
# Bonsai - OpenBIM Blender Add-on
|
2021-10-25 14:21:11 +11:00
|
|
|
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
|
|
|
|
#
|
2024-08-13 15:47:27 +10:00
|
|
|
# This file is part of Bonsai.
|
2021-10-25 14:21:11 +11:00
|
|
|
#
|
2024-08-13 15:47:27 +10:00
|
|
|
# Bonsai is free software: you can redistribute it and/or modify
|
2021-10-25 14:21:11 +11:00
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
# (at your option) any later version.
|
|
|
|
|
#
|
2024-08-13 15:47:27 +10:00
|
|
|
# Bonsai is distributed in the hope that it will be useful,
|
2021-10-25 14:21:11 +11:00
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
|
#
|
|
|
|
|
# You should have received a copy of the GNU General Public License
|
2024-08-13 15:47:27 +10:00
|
|
|
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
2021-10-25 14:21:11 +11:00
|
|
|
|
|
|
|
|
import bpy
|
|
|
|
|
import ifcopenshell
|
2025-06-09 10:59:22 +05:00
|
|
|
import ifcopenshell.api
|
|
|
|
|
import ifcopenshell.api.pset
|
2026-08-10 11:01:56 -07:00
|
|
|
import ifcopenshell.api.root
|
|
|
|
|
import ifcopenshell.api.unit
|
2026-01-26 17:11:12 +05:00
|
|
|
|
2024-08-14 13:56:39 +05:00
|
|
|
import bonsai.core.tool
|
|
|
|
|
import bonsai.tool as tool
|
|
|
|
|
from bonsai.tool.pset import Pset as subject
|
2021-10-25 14:21:11 +11:00
|
|
|
from test.bim.bootstrap import NewFile
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestImplementsTool(NewFile):
|
|
|
|
|
def test_run(self):
|
2024-08-14 13:56:39 +05:00
|
|
|
assert isinstance(subject(), bonsai.core.tool.Pset)
|
2021-10-25 14:21:11 +11:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestGetElementPset(NewFile):
|
|
|
|
|
def test_run(self):
|
|
|
|
|
ifc = ifcopenshell.file()
|
|
|
|
|
tool.Ifc.set(ifc)
|
|
|
|
|
element = ifc.createIfcWall()
|
2025-06-09 10:59:22 +05:00
|
|
|
pset = ifcopenshell.api.pset.add_pset(ifc, product=element, name="Foo")
|
2021-10-25 14:21:11 +11:00
|
|
|
assert subject.get_element_pset(element, "Foo") == pset
|
2023-03-19 13:13:15 +11:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestIsPsetEmpty(NewFile):
|
|
|
|
|
def test_run(self):
|
|
|
|
|
ifc = ifcopenshell.file()
|
|
|
|
|
tool.Ifc.set(ifc)
|
|
|
|
|
element = ifc.createIfcWall()
|
2025-06-09 10:59:22 +05:00
|
|
|
pset = ifcopenshell.api.pset.add_pset(ifc, product=element, name="Foo")
|
2023-03-19 13:13:15 +11:00
|
|
|
assert subject.is_pset_empty(pset) is True
|
2025-06-09 10:59:22 +05:00
|
|
|
ifcopenshell.api.pset.edit_pset(ifc, pset=pset, properties={"Foo": "Bar"})
|
2023-03-19 13:13:15 +11:00
|
|
|
assert subject.is_pset_empty(pset) is False
|
2025-06-09 10:59:22 +05:00
|
|
|
ifcopenshell.api.pset.edit_pset(ifc, pset=pset, properties={"Foo": None})
|
2023-03-19 13:13:15 +11:00
|
|
|
assert subject.is_pset_empty(pset) is True
|
2026-08-10 11:01:56 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestEditingAnOverriddenUnitPropertyRoundTrips(NewFile):
|
|
|
|
|
def test_run(self):
|
|
|
|
|
# Project default is mm, but this property is authored directly in m.
|
|
|
|
|
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])
|
|
|
|
|
length_m = ifcopenshell.api.unit.add_si_unit(ifc, unit_type="LENGTHUNIT")
|
|
|
|
|
|
|
|
|
|
element = ifc.createIfcWall()
|
|
|
|
|
pset = ifcopenshell.api.pset.add_pset(ifc, product=element, name="Pset_Test")
|
|
|
|
|
prop = ifc.createIfcPropertySingleValue(
|
|
|
|
|
Name="Foo", NominalValue=ifc.createIfcLengthMeasure(2.5), Unit=length_m
|
|
|
|
|
)
|
|
|
|
|
pset.HasProperties = [prop]
|
|
|
|
|
|
|
|
|
|
obj = bpy.data.objects.new("Wall", None)
|
|
|
|
|
tool.Ifc.link(element, obj)
|
|
|
|
|
blender_props = obj.PsetProperties
|
|
|
|
|
subject.import_pset_from_existing(pset, blender_props, None)
|
|
|
|
|
|
|
|
|
|
metadata = blender_props.properties["Foo"].metadata
|
|
|
|
|
assert metadata.unit_symbol == "m"
|
|
|
|
|
assert metadata.float_value == 2.5 # raw stored value, not rescaled to the project's mm
|
|
|
|
|
|
|
|
|
|
# Simulate a user edit in the property editor.
|
|
|
|
|
metadata.float_value = 3.5
|
|
|
|
|
|
|
|
|
|
# Simulate what EditPset.execute() does: collect the raw value straight
|
|
|
|
|
# off the metadata and write it back, with no rescaling step.
|
|
|
|
|
properties = {"Foo": metadata.get_value()}
|
|
|
|
|
ifcopenshell.api.pset.edit_pset(ifc, pset=pset, properties=properties)
|
|
|
|
|
|
|
|
|
|
assert prop.NominalValue.wrappedValue == 3.5 # not rescaled to 3500mm
|
|
|
|
|
assert prop.Unit == length_m # override preserved
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestImportingATemplatedQuantityRespectsItsOwnUnitOverride(NewFile):
|
|
|
|
|
def test_run(self):
|
|
|
|
|
# Regression test: import_pset_from_template's Q_ branch used to
|
|
|
|
|
# unconditionally re-template existing quantities, which shadowed
|
|
|
|
|
# their own Unit override with the project default -- edit mode
|
|
|
|
|
# showed "m" while the read-only panel correctly showed "mm".
|
|
|
|
|
# Project default is m, but this quantity is authored directly in mm.
|
|
|
|
|
ifc = ifcopenshell.file()
|
|
|
|
|
tool.Ifc.set(ifc)
|
|
|
|
|
ifcopenshell.api.root.create_entity(ifc, ifc_class="IfcProject")
|
|
|
|
|
length_m = ifcopenshell.api.unit.add_si_unit(ifc, unit_type="LENGTHUNIT")
|
|
|
|
|
ifcopenshell.api.unit.assign_unit(ifc, units=[length_m])
|
|
|
|
|
length_mm = ifcopenshell.api.unit.add_si_unit(ifc, unit_type="LENGTHUNIT", prefix="MILLI")
|
|
|
|
|
|
|
|
|
|
element = ifc.createIfcBeam()
|
|
|
|
|
qto = ifcopenshell.api.pset.add_qto(ifc, product=element, name="Qto_Test")
|
|
|
|
|
quantity = ifc.createIfcQuantityLength(Name="Foo", Unit=length_mm, LengthValue=2500.0)
|
|
|
|
|
qto.Quantities = [quantity]
|
|
|
|
|
|
|
|
|
|
pset_template = ifc.createIfcPropertySetTemplate(
|
|
|
|
|
Name="Qto_Test",
|
|
|
|
|
TemplateType="PSET_TYPEDRIVENOVERRIDE",
|
|
|
|
|
ApplicableEntity="IfcBeam",
|
|
|
|
|
HasPropertyTemplates=[ifc.createIfcSimplePropertyTemplate(Name="Foo", TemplateType="Q_LENGTH")],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
obj = bpy.data.objects.new("Beam", None)
|
|
|
|
|
tool.Ifc.link(element, obj)
|
|
|
|
|
blender_props = obj.PsetProperties
|
|
|
|
|
# Mirrors core/pset.py's enable_pset_editing: template pass, then existing-data pass.
|
|
|
|
|
subject.import_pset_from_template(pset_template, qto, blender_props)
|
|
|
|
|
subject.import_pset_from_existing(qto, blender_props, pset_template)
|
|
|
|
|
|
|
|
|
|
assert len(blender_props.properties) == 1 # not duplicated by the template pass
|
|
|
|
|
metadata = blender_props.properties["Foo"].metadata
|
|
|
|
|
assert metadata.unit_symbol == "mm" # the quantity's own override, not the project default "m"
|
|
|
|
|
assert metadata.float_value == 2500.0 # raw stored value, not rescaled
|