diff --git a/src/blenderbim/blenderbim/bim/helper.py b/src/blenderbim/blenderbim/bim/helper.py index b56998d388..0a0bbdcec0 100644 --- a/src/blenderbim/blenderbim/bim/helper.py +++ b/src/blenderbim/blenderbim/bim/helper.py @@ -46,7 +46,7 @@ def draw_attribute(attribute, layout, copy_operator=None): layout.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") if copy_operator: op = layout.operator(f"{copy_operator}", text="", icon="COPYDOWN") - op.attribute_name = attribute.name + op.name = attribute.name def import_attributes(ifc_class, props, data, callback=None): diff --git a/src/blenderbim/blenderbim/bim/module/attribute/operator.py b/src/blenderbim/blenderbim/bim/module/attribute/operator.py index d96d2aa57f..b06efbc939 100644 --- a/src/blenderbim/blenderbim/bim/module/attribute/operator.py +++ b/src/blenderbim/blenderbim/bim/module/attribute/operator.py @@ -139,9 +139,9 @@ class GenerateGlobalId(bpy.types.Operator): class CopyAttributeToSelection(bpy.types.Operator, Operator): bl_idname = "bim.copy_attribute_to_selection" bl_label = "Copy Attribute To Selection" - attribute_name: bpy.props.StringProperty() + name: bpy.props.StringProperty() def _execute(self, context): - value = context.active_object.BIMAttributeProperties.attributes.get(self.attribute_name).get_value() + value = context.active_object.BIMAttributeProperties.attributes.get(self.name).get_value() for obj in context.selected_objects: - core.copy_attribute_to_selection(tool.Ifc, name=self.attribute_name, value=value, obj=obj) + core.copy_attribute_to_selection(tool.Ifc, name=self.name, value=value, obj=obj) diff --git a/src/blenderbim/blenderbim/bim/module/pset/__init__.py b/src/blenderbim/blenderbim/bim/module/pset/__init__.py index 8c360d3aa8..b1dcfa4ec2 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/pset/__init__.py @@ -20,14 +20,15 @@ import bpy from . import ui, prop, operator classes = ( - operator.TogglePsetExpansion, - operator.EnablePsetEditing, - operator.DisablePsetEditing, - operator.EditPset, - operator.RemovePset, operator.AddPset, operator.AddQto, + operator.CopyPropertyToSelection, + operator.DisablePsetEditing, + operator.EditPset, + operator.EnablePsetEditing, operator.GuessQuantity, + operator.RemovePset, + operator.TogglePsetExpansion, prop.PsetProperties, prop.MaterialPsetProperties, prop.TaskPsetProperties, diff --git a/src/blenderbim/blenderbim/bim/module/pset/operator.py b/src/blenderbim/blenderbim/bim/module/pset/operator.py index fc9fd6386a..f30f0e77fe 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/operator.py +++ b/src/blenderbim/blenderbim/bim/module/pset/operator.py @@ -24,12 +24,20 @@ import ifcopenshell.util.pset import ifcopenshell.util.attribute import blenderbim.bim.schema import blenderbim.tool as tool +import blenderbim.core.pset as core from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.pset.data import Data from ifcopenshell.api.cost.data import Data as CostData from blenderbim.bim.module.pset.qto_calculator import QtoCalculator +class Operator: + def execute(self, context): + IfcStore.execute_ifc_operator(self, context) + blenderbim.bim.handler.refresh_ui_data() + return {"FINISHED"} + + def get_pset_props(context, obj, obj_type): if obj_type == "Object": obj = bpy.data.objects.get(obj) @@ -361,3 +369,17 @@ class GuessQuantity(bpy.types.Operator): if unit_settings.length_unit == "METERS": return None, "METRE" return unit_settings.length_unit[0 : -len("METERS")], "METRE" + + +class CopyPropertyToSelection(bpy.types.Operator, Operator): + bl_idname = "bim.copy_property_to_selection" + bl_label = "Copy Property To Selection" + name: bpy.props.StringProperty() + + def _execute(self, context): + pset_name = context.active_object.PsetProperties.active_pset_name + prop_value = context.active_object.PsetProperties.properties.get(self.name).get_value() + for obj in context.selected_objects: + core.copy_property_to_selection( + tool.Ifc, tool.Pset, obj=obj, pset_name=pset_name, prop_name=self.name, prop_value=prop_value + ) diff --git a/src/blenderbim/blenderbim/bim/module/pset/prop.py b/src/blenderbim/blenderbim/bim/module/pset/prop.py index f47cadd87e..ba96a34555 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/prop.py +++ b/src/blenderbim/blenderbim/bim/module/pset/prop.py @@ -50,6 +50,8 @@ def getPsetNames(self, context): obj = context.active_object if not obj.BIMObjectProperties.ifc_definition_id: return [] + if obj.BIMObjectProperties.ifc_definition_id not in Data.products: + Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id) element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id) ifc_class = element.is_a() if ifc_class not in psetnames: diff --git a/src/blenderbim/blenderbim/bim/module/pset/ui.py b/src/blenderbim/blenderbim/bim/module/pset/ui.py index 90f5791adf..27e423c3c2 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/ui.py +++ b/src/blenderbim/blenderbim/bim/module/pset/ui.py @@ -87,7 +87,7 @@ def draw_psetqto_ui(context, pset_id, pset, props, layout, obj_type): def draw_psetqto_editable_ui(box, props, prop): row = box.row(align=True) - draw_attribute(prop, row) + draw_attribute(prop, row, copy_operator="bim.copy_property_to_selection") if ( "length" in prop.name.lower() or "width" in prop.name.lower() diff --git a/src/blenderbim/blenderbim/bim/operator.py b/src/blenderbim/blenderbim/bim/operator.py index d7264c8f7a..b3921ff817 100644 --- a/src/blenderbim/blenderbim/bim/operator.py +++ b/src/blenderbim/blenderbim/bim/operator.py @@ -425,37 +425,6 @@ class FetchObjectPassport(bpy.types.Operator): context.active_object.data = bpy.data.meshes[reference.name] -class CopyPropertyToSelection(bpy.types.Operator): - bl_idname = "bim.copy_property_to_selection" - bl_label = "Copy Property To Selection" - pset_name: bpy.props.StringProperty() - prop_name: bpy.props.StringProperty() - prop_value: bpy.props.StringProperty() - - def execute(self, context): - # TODO: this is dead code, awaiting reimplementation. See #1222. - for obj in context.selected_objects: - if "/" not in obj.name: - continue - pset = obj.BIMObjectProperties.psets.get(self.pset_name) - if not pset: - applicable_psets = schema.ifc.psetqto.get_applicable(obj.name.split("/")[0], pset_only=True) - for pset_template in applicable_psets: - if pset_template.Name == self.pset_name: - break - else: - continue - pset = obj.BIMObjectProperties.psets.add() - pset.name = self.pset_name - for template_prop_name in (p.Name for p in pset_template.HasPropertyTemplates): - prop = pset.properties.add() - prop.name = template_prop_name - prop = pset.properties.get(self.prop_name) - if prop: - prop.string_value = self.prop_value - return {"FINISHED"} - - class ConfigureVisibility(bpy.types.Operator): bl_idname = "bim.configure_visibility" bl_label = "Configure module UI visibility in BlenderBIM" diff --git a/src/blenderbim/blenderbim/core/pset.py b/src/blenderbim/blenderbim/core/pset.py new file mode 100644 index 0000000000..b568e05d29 --- /dev/null +++ b/src/blenderbim/blenderbim/core/pset.py @@ -0,0 +1,27 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# 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. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# 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 +# along with BlenderBIM Add-on. If not, see . + + +def copy_property_to_selection(ifc, pset, obj=None, pset_name=None, prop_name=None, prop_value=None): + element = ifc.get_entity(obj) + if not element: + return + ifc_pset = pset.get_element_pset(element, pset_name) + if not ifc_pset: + ifc_pset = ifc.run("pset.add_pset", product=element, name=pset_name) + ifc.run("pset.edit_pset", pset=ifc_pset, properties={prop_name: prop_value}) diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index e90297d42d..3f07ad85d9 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -139,6 +139,11 @@ class Owner: def set_user(cls, user): pass +@interface +class Pset: + def get_element_pset(cls, element, pset_name): pass + + @interface class Qto: def get_radius_of_selected_vertices(cls, obj): pass diff --git a/src/blenderbim/blenderbim/tool/__init__.py b/src/blenderbim/blenderbim/tool/__init__.py index 521b4d1854..a74888f16a 100644 --- a/src/blenderbim/blenderbim/tool/__init__.py +++ b/src/blenderbim/blenderbim/tool/__init__.py @@ -25,6 +25,7 @@ from blenderbim.tool.ifc import Ifc from blenderbim.tool.material import Material from blenderbim.tool.misc import Misc from blenderbim.tool.owner import Owner +from blenderbim.tool.pset import Pset from blenderbim.tool.qto import Qto from blenderbim.tool.root import Root from blenderbim.tool.spatial import Spatial diff --git a/src/blenderbim/blenderbim/tool/pset.py b/src/blenderbim/blenderbim/tool/pset.py new file mode 100644 index 0000000000..707ef8102f --- /dev/null +++ b/src/blenderbim/blenderbim/tool/pset.py @@ -0,0 +1,31 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# 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. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# 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 +# along with BlenderBIM Add-on. If not, see . + +import bpy +import ifcopenshell +import blenderbim.core.tool +import blenderbim.tool as tool + + +class Pset(blenderbim.core.tool.Pset): + @classmethod + def get_element_pset(cls, element, pset_name): + psets = ifcopenshell.util.element.get_psets(element) + pset = psets.get(pset_name, None) + if pset: + return tool.Ifc.get().by_id(pset["id"]) diff --git a/src/blenderbim/pytest.ini b/src/blenderbim/pytest.ini index ae78d9a487..26176e123f 100644 --- a/src/blenderbim/pytest.ini +++ b/src/blenderbim/pytest.ini @@ -11,6 +11,7 @@ markers = owner patch project + pset pset_template qto root diff --git a/src/blenderbim/test/bim/feature/attribute.feature b/src/blenderbim/test/bim/feature/attribute.feature index 742ad302a9..99aeec78d4 100644 --- a/src/blenderbim/test/bim/feature/attribute.feature +++ b/src/blenderbim/test/bim/feature/attribute.feature @@ -15,5 +15,5 @@ Scenario: Copy attribute to selected And additionally the object "IfcWall/Cube.001" is selected And I press "bim.enable_editing_attributes(obj='IfcWall/Cube.001', obj_type='Object')" And I set "active_object.BIMAttributeProperties.attributes[2].string_value" to "Foo" - When I press "bim.copy_attribute_to_selection(attribute_name='Description')" + When I press "bim.copy_attribute_to_selection(name='Description')" Then nothing happens diff --git a/src/blenderbim/test/bim/feature/pset.feature b/src/blenderbim/test/bim/feature/pset.feature new file mode 100644 index 0000000000..e5f624ceb0 --- /dev/null +++ b/src/blenderbim/test/bim/feature/pset.feature @@ -0,0 +1,22 @@ +@pset +Feature: Pset + +Scenario: Copy property to selected + Given an empty IFC project + And I add a cube + And the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + And I add a cube + And the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + And the object "IfcWall/Cube" is selected + And additionally the object "IfcWall/Cube.001" is selected + And I set "active_object.PsetProperties.pset_name" to "Pset_BuildingElementCommon" + And I press "bim.add_pset(obj='IfcWall/Cube.001', obj_type='Object')" + And the variable "pset" is "{ifc}.by_type('IfcPropertySet')[-1].id()" + And I press "bim.enable_pset_editing(obj='IfcWall/Cube.001', obj_type='Object', pset_id={pset})" + And I set "active_object.PsetProperties.properties[2].string_value" to "Foo" + When I press "bim.copy_property_to_selection(name='FireRating')" + Then nothing happens diff --git a/src/blenderbim/test/core/bootstrap.py b/src/blenderbim/test/core/bootstrap.py index 261337e63b..4b32a6e8af 100644 --- a/src/blenderbim/test/core/bootstrap.py +++ b/src/blenderbim/test/core/bootstrap.py @@ -84,6 +84,13 @@ def owner(): prophet.verify() +@pytest.fixture +def pset(): + prophet = Prophecy(blenderbim.core.tool.Pset) + yield prophet + prophet.verify() + + @pytest.fixture def qto(): prophet = Prophecy(blenderbim.core.tool.Qto) diff --git a/src/blenderbim/test/core/test_pset.py b/src/blenderbim/test/core/test_pset.py new file mode 100644 index 0000000000..f12dc96858 --- /dev/null +++ b/src/blenderbim/test/core/test_pset.py @@ -0,0 +1,45 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# 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. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# 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 +# along with BlenderBIM Add-on. If not, see . + +import blenderbim.core.pset as subject +from test.core.bootstrap import ifc, pset + + +class TestCopyPropertyToSelection: + def test_doing_nothing_if_object_is_not_an_element(self, ifc, pset): + ifc.get_entity("obj").should_be_called().will_return(None) + subject.copy_property_to_selection( + ifc, pset, obj="obj", pset_name="pset_name", prop_name="prop_name", prop_value="prop_value" + ) + + def test_copying_the_property_to_an_existing_pset(self, ifc, pset): + ifc.get_entity("obj").should_be_called().will_return("element") + pset.get_element_pset("element", "pset_name").should_be_called().will_return("pset") + ifc.run("pset.edit_pset", pset="pset", properties={"prop_name": "prop_value"}).should_be_called() + subject.copy_property_to_selection( + ifc, pset, obj="obj", pset_name="pset_name", prop_name="prop_name", prop_value="prop_value" + ) + + def test_creating_a_new_pset_if_it_doesnt_exist(self, ifc, pset): + ifc.get_entity("obj").should_be_called().will_return("element") + pset.get_element_pset("element", "pset_name").should_be_called().will_return(None) + ifc.run("pset.add_pset", product="element", name="pset_name").should_be_called().will_return("pset") + ifc.run("pset.edit_pset", pset="pset", properties={"prop_name": "prop_value"}).should_be_called() + subject.copy_property_to_selection( + ifc, pset, obj="obj", pset_name="pset_name", prop_name="prop_name", prop_value="prop_value" + ) diff --git a/src/blenderbim/test/tool/test_pset.py b/src/blenderbim/test/tool/test_pset.py new file mode 100644 index 0000000000..32d0287458 --- /dev/null +++ b/src/blenderbim/test/tool/test_pset.py @@ -0,0 +1,38 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# 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. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# 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 +# along with BlenderBIM Add-on. If not, see . + +import bpy +import ifcopenshell +import blenderbim.core.tool +import blenderbim.tool as tool +from blenderbim.tool.pset import Pset as subject +from test.bim.bootstrap import NewFile + + +class TestImplementsTool(NewFile): + def test_run(self): + assert isinstance(subject(), blenderbim.core.tool.Pset) + + +class TestGetElementPset(NewFile): + def test_run(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + element = ifc.createIfcWall() + pset = ifcopenshell.api.run("pset.add_pset", ifc, product=element, name="Foo") + assert subject.get_element_pset(element, "Foo") == pset