From f26d43e0cd5dcaaf909d6d7060e3fb85cbdea241 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 7 Oct 2021 18:51:50 +1100 Subject: [PATCH] Prioritise editing rendering surface styles over shading surface styles --- src/blenderbim/blenderbim/bim/helper.py | 62 +-- .../blenderbim/bim/module/aggregate/data.py | 18 + .../bim/module/aggregate/operator.py | 6 +- .../blenderbim/bim/module/aggregate/prop.py | 18 + .../blenderbim/bim/module/context/operator.py | 6 +- .../bim/module/geometry/operator.py | 2 +- .../blenderbim/bim/module/owner/operator.py | 32 +- .../blenderbim/bim/module/project/operator.py | 6 +- .../blenderbim/bim/module/root/operator.py | 8 +- .../module/style/data.py} | 38 +- .../blenderbim/bim/module/style/operator.py | 147 ++---- .../blenderbim/bim/module/style/prop.py | 2 +- .../blenderbim/bim/module/style/ui.py | 26 +- src/blenderbim/blenderbim/core/aggregate.py | 19 + src/blenderbim/blenderbim/core/context.py | 16 +- src/blenderbim/blenderbim/core/owner.py | 84 ++-- src/blenderbim/blenderbim/core/style.py | 69 +++ src/blenderbim/blenderbim/core/tool.py | 345 ++++---------- src/blenderbim/blenderbim/tool/__init__.py | 9 +- .../blenderbim/tool/address_editor.py | 108 ----- .../tool/{aggregator.py => aggregate.py} | 20 +- .../tool/{context_editor.py => context.py} | 2 +- .../blenderbim/tool/organisation_editor.py | 53 --- src/blenderbim/blenderbim/tool/owner.py | 196 ++++++++ .../blenderbim/tool/person_editor.py | 86 ---- src/blenderbim/blenderbim/tool/style.py | 114 +++++ src/blenderbim/test/bim/feature/style.feature | 59 +++ src/blenderbim/test/bim/test_feature.py | 5 + src/blenderbim/test/core/bootstrap.py | 43 +- src/blenderbim/test/core/test_aggregate.py | 40 +- src/blenderbim/test/core/test_context.py | 26 +- src/blenderbim/test/core/test_owner.py | 122 ++--- src/blenderbim/test/core/test_style.py | 98 ++++ .../test/tool/test_address_editor.py | 197 -------- .../{test_aggregator.py => test_aggregate.py} | 22 +- ...test_context_editor.py => test_context.py} | 4 +- .../test/tool/test_organisation_editor.py | 94 ---- src/blenderbim/test/tool/test_owner.py | 425 ++++++++++++++++++ .../test/tool/test_person_editor.py | 152 ------- src/blenderbim/test/tool/test_role_editor.py | 98 ---- src/blenderbim/test/tool/test_style.py | 183 ++++++++ .../ifcopenshell/api/style/add_style.py | 37 +- .../api/style/add_surface_style.py | 27 ++ .../api/style/edit_surface_style.py | 14 +- 44 files changed, 1670 insertions(+), 1468 deletions(-) rename src/blenderbim/blenderbim/{tool/role_editor.py => bim/module/style/data.py} (50%) create mode 100644 src/blenderbim/blenderbim/core/style.py delete mode 100644 src/blenderbim/blenderbim/tool/address_editor.py rename src/blenderbim/blenderbim/tool/{aggregator.py => aggregate.py} (61%) rename src/blenderbim/blenderbim/tool/{context_editor.py => context.py} (97%) delete mode 100644 src/blenderbim/blenderbim/tool/organisation_editor.py delete mode 100644 src/blenderbim/blenderbim/tool/person_editor.py create mode 100644 src/blenderbim/blenderbim/tool/style.py create mode 100644 src/blenderbim/test/bim/feature/style.feature create mode 100644 src/blenderbim/test/core/test_style.py delete mode 100644 src/blenderbim/test/tool/test_address_editor.py rename src/blenderbim/test/tool/{test_aggregator.py => test_aggregate.py} (82%) rename src/blenderbim/test/tool/{test_context_editor.py => test_context.py} (98%) delete mode 100644 src/blenderbim/test/tool/test_organisation_editor.py delete mode 100644 src/blenderbim/test/tool/test_person_editor.py delete mode 100644 src/blenderbim/test/tool/test_role_editor.py create mode 100644 src/blenderbim/test/tool/test_style.py create mode 100644 src/ifcopenshell-python/ifcopenshell/api/style/add_surface_style.py diff --git a/src/blenderbim/blenderbim/bim/helper.py b/src/blenderbim/blenderbim/bim/helper.py index e6eea8757d..04d07e0386 100644 --- a/src/blenderbim/blenderbim/bim/helper.py +++ b/src/blenderbim/blenderbim/bim/helper.py @@ -51,32 +51,42 @@ def draw_attribute(attribute, layout, copy_operator=None): def import_attributes(ifc_class, props, data, callback=None): for attribute in IfcStore.get_schema().declaration_by_name(ifc_class).all_attributes(): - data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) - if isinstance(data_type, tuple) or data_type == "entity": - callback(attribute.name(), None, data) if callback else None - continue - new = props.add() - new.name = attribute.name() - new.is_null = data[attribute.name()] is None - new.is_optional = attribute.optional() - new.data_type = data_type if isinstance(data_type, str) else "" - is_handled_by_callback = callback(attribute.name(), new, data) if callback else None - if is_handled_by_callback: - pass # Our job is done - elif is_handled_by_callback is False: - props.remove(len(props) - 1) - elif data_type == "string": - new.string_value = "" if new.is_null else data[attribute.name()] - elif data_type == "boolean": - new.bool_value = False if new.is_null else data[attribute.name()] - elif data_type == "integer": - new.int_value = 0 if new.is_null else data[attribute.name()] - elif data_type == "float": - new.float_value = 0.0 if new.is_null else data[attribute.name()] - elif data_type == "enum": - new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute)) - if data[attribute.name()]: - new.enum_value = data[attribute.name()] + import_attribute(attribute, props, data, callback=callback) + + +# A more elegant attribute importer signature, intended to supersede import_attributes +def import_attributes2(element, props, callback=None): + for attribute in element.wrapped_data.declaration().as_entity().all_attributes(): + import_attribute(attribute, props, element.get_info(), callback=callback) + + +def import_attribute(attribute, props, data, callback=None): + data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) + if isinstance(data_type, tuple) or data_type == "entity": + callback(attribute.name(), None, data) if callback else None + return + new = props.add() + new.name = attribute.name() + new.is_null = data[attribute.name()] is None + new.is_optional = attribute.optional() + new.data_type = data_type if isinstance(data_type, str) else "" + is_handled_by_callback = callback(attribute.name(), new, data) if callback else None + if is_handled_by_callback: + pass # Our job is done + elif is_handled_by_callback is False: + props.remove(len(props) - 1) + elif data_type == "string": + new.string_value = "" if new.is_null else data[attribute.name()] + elif data_type == "boolean": + new.bool_value = False if new.is_null else data[attribute.name()] + elif data_type == "integer": + new.int_value = 0 if new.is_null else data[attribute.name()] + elif data_type == "float": + new.float_value = 0.0 if new.is_null else data[attribute.name()] + elif data_type == "enum": + new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute)) + if data[attribute.name()]: + new.enum_value = data[attribute.name()] def export_attributes(props, callback=None): diff --git a/src/blenderbim/blenderbim/bim/module/aggregate/data.py b/src/blenderbim/blenderbim/bim/module/aggregate/data.py index d65f631586..095bfff16e 100644 --- a/src/blenderbim/blenderbim/bim/module/aggregate/data.py +++ b/src/blenderbim/blenderbim/bim/module/aggregate/data.py @@ -1,3 +1,21 @@ +# 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 blenderbim.tool as tool import ifcopenshell.util.element diff --git a/src/blenderbim/blenderbim/bim/module/aggregate/operator.py b/src/blenderbim/blenderbim/bim/module/aggregate/operator.py index 22ea870446..c5c5946324 100644 --- a/src/blenderbim/blenderbim/bim/module/aggregate/operator.py +++ b/src/blenderbim/blenderbim/bim/module/aggregate/operator.py @@ -40,7 +40,7 @@ class AssignObject(bpy.types.Operator, Operator): def _execute(self, context): core.assign_object( tool.Ifc, - tool.Aggregator, + tool.Aggregate, tool.Collector, relating_obj=tool.Ifc.get_object(tool.Ifc.get().by_id(self.relating_object)), related_obj=tool.Ifc.get_object(tool.Ifc.get().by_id(self.related_object)), @@ -69,7 +69,7 @@ class EnableEditingAggregate(bpy.types.Operator, Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - core.enable_editing_aggregate(tool.Aggregator, obj=context.active_object) + core.enable_editing_aggregate(tool.Aggregate, obj=context.active_object) class DisableEditingAggregate(bpy.types.Operator, Operator): @@ -78,7 +78,7 @@ class DisableEditingAggregate(bpy.types.Operator, Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - core.disable_editing_aggregate(tool.Aggregator, obj=context.active_object) + core.disable_editing_aggregate(tool.Aggregate, obj=context.active_object) class AddAggregate(bpy.types.Operator): diff --git a/src/blenderbim/blenderbim/bim/module/aggregate/prop.py b/src/blenderbim/blenderbim/bim/module/aggregate/prop.py index 8ecb77aeec..f7f0afa1cc 100644 --- a/src/blenderbim/blenderbim/bim/module/aggregate/prop.py +++ b/src/blenderbim/blenderbim/bim/module/aggregate/prop.py @@ -1,3 +1,21 @@ +# 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 from blenderbim.bim.prop import StrProperty, Attribute from blenderbim.bim.module.spatial.data import SpatialData diff --git a/src/blenderbim/blenderbim/bim/module/context/operator.py b/src/blenderbim/blenderbim/bim/module/context/operator.py index 974ca07008..20baef3b0f 100644 --- a/src/blenderbim/blenderbim/bim/module/context/operator.py +++ b/src/blenderbim/blenderbim/bim/module/context/operator.py @@ -68,7 +68,7 @@ class EnableEditingContext(bpy.types.Operator, Operator): context: bpy.props.IntProperty() def _execute(self, context): - core.enable_editing_context(tool.ContextEditor, context=tool.Ifc.get().by_id(self.context)) + core.enable_editing_context(tool.Context, context=tool.Ifc.get().by_id(self.context)) class DisableEditingContext(bpy.types.Operator, Operator): @@ -77,7 +77,7 @@ class DisableEditingContext(bpy.types.Operator, Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - core.disable_editing_context(tool.ContextEditor) + core.disable_editing_context(tool.Context) class EditContext(bpy.types.Operator, Operator): @@ -86,4 +86,4 @@ class EditContext(bpy.types.Operator, Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - core.edit_context(tool.Ifc, tool.ContextEditor) + core.edit_context(tool.Ifc, tool.Context) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index cd682f52a0..548370c0c9 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -68,7 +68,7 @@ class AddRepresentation(bpy.types.Operator): obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object self.file = IfcStore.get_file() - bpy.ops.bim.edit_object_placement(obj=obj.name) + core.edit_object_placement(tool.Ifc, tool.Surveyor, obj=obj) if not obj.data: return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/owner/operator.py b/src/blenderbim/blenderbim/bim/module/owner/operator.py index 692e395805..a2a668d6e9 100644 --- a/src/blenderbim/blenderbim/bim/module/owner/operator.py +++ b/src/blenderbim/blenderbim/bim/module/owner/operator.py @@ -39,7 +39,7 @@ class EnableEditingPerson(bpy.types.Operator, Operator): person: bpy.props.IntProperty() def _execute(self, context): - core.enable_editing_person(tool.PersonEditor, person=tool.Ifc.get().by_id(self.person)) + core.enable_editing_person(tool.Owner, person=tool.Ifc.get().by_id(self.person)) class DisableEditingPerson(bpy.types.Operator, Operator): @@ -48,7 +48,7 @@ class DisableEditingPerson(bpy.types.Operator, Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - core.disable_editing_person(tool.PersonEditor) + core.disable_editing_person(tool.Owner) class AddPerson(bpy.types.Operator, Operator): @@ -66,7 +66,7 @@ class EditPerson(bpy.types.Operator, Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - core.edit_person(tool.Ifc, tool.PersonEditor) + core.edit_person(tool.Ifc, tool.Owner) class RemovePerson(bpy.types.Operator, Operator): @@ -86,7 +86,7 @@ class AddPersonAttribute(bpy.types.Operator, Operator): name: bpy.props.StringProperty() def _execute(self, context): - core.add_person_attribute(tool.PersonEditor, name=self.name) + core.add_person_attribute(tool.Owner, name=self.name) class RemovePersonAttribute(bpy.types.Operator, Operator): @@ -97,7 +97,7 @@ class RemovePersonAttribute(bpy.types.Operator, Operator): id: bpy.props.IntProperty() def _execute(self, context): - core.remove_person_attribute(tool.PersonEditor, name=self.name, id=self.id) + core.remove_person_attribute(tool.Owner, name=self.name, id=self.id) class EnableEditingRole(bpy.types.Operator, Operator): @@ -107,7 +107,7 @@ class EnableEditingRole(bpy.types.Operator, Operator): role: bpy.props.IntProperty() def _execute(self, context): - core.enable_editing_role(tool.RoleEditor, role=tool.Ifc.get().by_id(self.role)) + core.enable_editing_role(tool.Owner, role=tool.Ifc.get().by_id(self.role)) class DisableEditingRole(bpy.types.Operator, Operator): @@ -116,7 +116,7 @@ class DisableEditingRole(bpy.types.Operator, Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - core.disable_editing_role(tool.RoleEditor) + core.disable_editing_role(tool.Owner) class AddRole(bpy.types.Operator, Operator): @@ -135,7 +135,7 @@ class EditRole(bpy.types.Operator, Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - core.edit_role(tool.Ifc, tool.RoleEditor) + core.edit_role(tool.Ifc, tool.Owner) class RemoveRole(bpy.types.Operator, Operator): @@ -166,7 +166,7 @@ class AddAddressAttribute(bpy.types.Operator, Operator): name: bpy.props.StringProperty() def _execute(self, context): - core.add_address_attribute(tool.AddressEditor, name=self.name) + core.add_address_attribute(tool.Owner, name=self.name) class RemoveAddressAttribute(bpy.types.Operator, Operator): @@ -177,7 +177,7 @@ class RemoveAddressAttribute(bpy.types.Operator, Operator): id: bpy.props.IntProperty() def _execute(self, context): - core.remove_address_attribute(tool.AddressEditor, name=self.name, id=self.id) + core.remove_address_attribute(tool.Owner, name=self.name, id=self.id) class EnableEditingAddress(bpy.types.Operator, Operator): @@ -187,7 +187,7 @@ class EnableEditingAddress(bpy.types.Operator, Operator): address: bpy.props.IntProperty() def _execute(self, context): - core.enable_editing_address(tool.AddressEditor, address=tool.Ifc.get().by_id(self.address)) + core.enable_editing_address(tool.Owner, address=tool.Ifc.get().by_id(self.address)) class DisableEditingAddress(bpy.types.Operator, Operator): @@ -196,7 +196,7 @@ class DisableEditingAddress(bpy.types.Operator, Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - core.disable_editing_address(tool.AddressEditor) + core.disable_editing_address(tool.Owner) class EditAddress(bpy.types.Operator, Operator): @@ -205,7 +205,7 @@ class EditAddress(bpy.types.Operator, Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - core.edit_address(tool.Ifc, tool.AddressEditor) + core.edit_address(tool.Ifc, tool.Owner) class RemoveAddress(bpy.types.Operator, Operator): @@ -225,7 +225,7 @@ class EnableEditingOrganisation(bpy.types.Operator, Operator): organisation: bpy.props.IntProperty() def _execute(self, context): - core.enable_editing_organisation(tool.OrganisationEditor, organisation=tool.Ifc.get().by_id(self.organisation)) + core.enable_editing_organisation(tool.Owner, organisation=tool.Ifc.get().by_id(self.organisation)) class DisableEditingOrganisation(bpy.types.Operator, Operator): @@ -234,7 +234,7 @@ class DisableEditingOrganisation(bpy.types.Operator, Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - core.disable_editing_organisation(tool.OrganisationEditor) + core.disable_editing_organisation(tool.Owner) class AddOrganisation(bpy.types.Operator, Operator): @@ -252,7 +252,7 @@ class EditOrganisation(bpy.types.Operator, Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - core.edit_organisation(tool.Ifc, tool.OrganisationEditor) + core.edit_organisation(tool.Ifc, tool.Owner) class RemoveOrganisation(bpy.types.Operator, Operator): diff --git a/src/blenderbim/blenderbim/bim/module/project/operator.py b/src/blenderbim/blenderbim/bim/module/project/operator.py index b2b5decfc2..f855edb95f 100644 --- a/src/blenderbim/blenderbim/bim/module/project/operator.py +++ b/src/blenderbim/blenderbim/bim/module/project/operator.py @@ -96,13 +96,13 @@ class CreateProject(bpy.types.Operator): bpy.ops.bim.assign_class(obj=building_storey.name, ifc_class="IfcBuildingStorey") blenderbim.core.aggregate.assign_object( - tool.Ifc, tool.Aggregator, tool.Collector, relating_obj=project, related_obj=site + tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=project, related_obj=site ) blenderbim.core.aggregate.assign_object( - tool.Ifc, tool.Aggregator, tool.Collector, relating_obj=site, related_obj=building + tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=site, related_obj=building ) blenderbim.core.aggregate.assign_object( - tool.Ifc, tool.Aggregator, tool.Collector, relating_obj=building, related_obj=building_storey + tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=building, related_obj=building_storey ) context.view_layer.objects.active = active_object diff --git a/src/blenderbim/blenderbim/bim/module/root/operator.py b/src/blenderbim/blenderbim/bim/module/root/operator.py index 5f274062a7..dc8c0e1932 100644 --- a/src/blenderbim/blenderbim/bim/module/root/operator.py +++ b/src/blenderbim/blenderbim/bim/module/root/operator.py @@ -204,7 +204,13 @@ class AssignClass(bpy.types.Operator): collection.objects.link(obj) if parent_collection: parent_collection.children.link(collection) - bpy.ops.bim.assign_object(related_object=obj.name, relating_object=parent_collection.name) + blenderbim.core.aggregate.assign_object( + tool.Ifc, + tool.Aggregator, + tool.Collector, + relating_obj=bpy.data.objects.get(parent_collection.name), + related_obj=obj, + ) else: context.scene.collection.children.link(collection) diff --git a/src/blenderbim/blenderbim/tool/role_editor.py b/src/blenderbim/blenderbim/bim/module/style/data.py similarity index 50% rename from src/blenderbim/blenderbim/tool/role_editor.py rename to src/blenderbim/blenderbim/bim/module/style/data.py index cdd98acf1f..78714b7fcd 100644 --- a/src/blenderbim/blenderbim/tool/role_editor.py +++ b/src/blenderbim/blenderbim/bim/module/style/data.py @@ -17,31 +17,27 @@ # along with BlenderBIM Add-on. If not, see . import bpy -import blenderbim.core.tool import blenderbim.tool as tool -import blenderbim.bim.helper -class RoleEditor(blenderbim.core.tool.RoleEditor): - @classmethod - def set_role(cls, role): - bpy.context.scene.BIMOwnerProperties.active_role_id = role.id() +def refresh(): + StyleAttributesData.is_loaded = False + + +class StyleAttributesData: + data = {} + is_loaded = False @classmethod - def import_attributes(cls): - role = cls.get_role() - props = bpy.context.scene.BIMOwnerProperties - props.role_attributes.clear() - blenderbim.bim.helper.import_attributes("IfcActorRole", props.role_attributes, role.get_info()) + def load(cls): + cls.data = {"attributes": cls.get_attributes()} @classmethod - def clear_role(cls): - bpy.context.scene.BIMOwnerProperties.active_role_id = 0 - - @classmethod - def get_role(cls): - return tool.Ifc().get().by_id(bpy.context.scene.BIMOwnerProperties.active_role_id) - - @classmethod - def export_attributes(cls): - return blenderbim.bim.helper.export_attributes(bpy.context.scene.BIMOwnerProperties.role_attributes) + def get_attributes(cls): + style = tool.Ifc.get().by_id(bpy.context.active_object.active_material.BIMMaterialProperties.ifc_style_id) + results = [] + for name, value in style.get_info().items(): + if name in ["id", "type", "Styles"]: + continue + results.append({"name": name, "value": str(value)}) + return results diff --git a/src/blenderbim/blenderbim/bim/module/style/operator.py b/src/blenderbim/blenderbim/bim/module/style/operator.py index c03376c187..cc7c2d6b81 100644 --- a/src/blenderbim/blenderbim/bim/module/style/operator.py +++ b/src/blenderbim/blenderbim/bim/module/style/operator.py @@ -17,174 +17,77 @@ # along with BlenderBIM Add-on. If not, see . import bpy -import ifcopenshell.api -import ifcopenshell.util.representation -import blenderbim.bim.helper +import blenderbim.bim.handler +import blenderbim.tool as tool +import blenderbim.core.style as core from blenderbim.bim.ifc import IfcStore -from ifcopenshell.api.style.data import Data -def get_colour_settings(material): - transparency = material.diffuse_color[3] - diffuse_colour = material.diffuse_color - if material.use_nodes and hasattr(material.node_tree, "nodes") and "Principled BSDF" in material.node_tree.nodes: - bsdf = material.node_tree.nodes["Principled BSDF"] - transparency = bsdf.inputs["Alpha"].default_value - diffuse_colour = bsdf.inputs["Base Color"].default_value - transparency = 1 - transparency - return { - "surface_colour": tuple(material.diffuse_color), - "transparency": transparency, - "diffuse_colour": tuple(diffuse_colour), - } +class Operator: + def execute(self, context): + IfcStore.execute_ifc_operator(self, context) + blenderbim.bim.handler.refresh_ui_data() + return {"FINISHED"} -class UpdateStyleColours(bpy.types.Operator): +class UpdateStyleColours(bpy.types.Operator, Operator): bl_idname = "bim.update_style_colours" bl_label = "Update Style Colours" bl_options = {"REGISTER", "UNDO"} - material: bpy.props.StringProperty() - - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) def _execute(self, context): - self.file = IfcStore.get_file() - material = bpy.data.materials.get(self.material) if self.material else context.active_object.active_material - settings = get_colour_settings(material) - for style in self.file.by_id(material.BIMMaterialProperties.ifc_style_id).Styles: - if style.is_a("IfcSurfaceStyleRendering"): - ifcopenshell.api.run( - "style.edit_surface_style", - self.file, - style=style, - attributes={ - "SurfaceColour": settings["surface_colour"], - "Transparency": settings["transparency"], - "DiffuseColour": settings["diffuse_colour"], - }, - ) - elif style.is_a("IfcSurfaceStyleShading"): - ifcopenshell.api.run( - "style.edit_surface_style", - self.file, - style=style, - attributes={"SurfaceColour": settings["surface_colour"], "Transparency": settings["transparency"]}, - ) - return {"FINISHED"} + core.update_style_colours(tool.Ifc, tool.Style, obj=context.active_object.active_material) -class RemoveStyle(bpy.types.Operator): +class RemoveStyle(bpy.types.Operator, Operator): bl_idname = "bim.remove_style" bl_label = "Remove Style" bl_options = {"REGISTER", "UNDO"} - material: bpy.props.StringProperty() - - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) def _execute(self, context): - self.file = IfcStore.get_file() - material = bpy.data.materials.get(self.material) if self.material else context.active_object.active_material - ifcopenshell.api.run( - "style.remove_style", self.file, style=self.file.by_id(material.BIMMaterialProperties.ifc_style_id) - ) - material.BIMMaterialProperties.ifc_style_id = 0 - return {"FINISHED"} + core.remove_style(tool.Ifc, tool.Style, obj=context.active_object.active_material) -class AddStyle(bpy.types.Operator): +class AddStyle(bpy.types.Operator, Operator): bl_idname = "bim.add_style" bl_label = "Add Style" bl_options = {"REGISTER", "UNDO"} - material: bpy.props.StringProperty() - - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) def _execute(self, context): - self.file = IfcStore.get_file() - material = bpy.data.materials.get(self.material) if self.material else context.active_object.active_material - settings = get_colour_settings(material) - settings["name"] = material.name - settings["external_definition"] = None # TODO: Implement. See #1222 - style = ifcopenshell.api.run("style.add_style", self.file, **settings) - IfcStore.link_element(style, material) - if material.BIMObjectProperties.ifc_definition_id: - context = ifcopenshell.util.representation.get_context(self.file, "Model", "Body", "MODEL_VIEW") - if context: - ifcopenshell.api.run( - "style.assign_material_style", - self.file, - **{ - "material": self.file.by_id(material.BIMObjectProperties.ifc_definition_id), - "style": style, - "context": context, - } - ) - return {"FINISHED"} + core.add_style(tool.Ifc, tool.Style, obj=context.active_object.active_material) -class UnlinkStyle(bpy.types.Operator): +class UnlinkStyle(bpy.types.Operator, Operator): bl_idname = "bim.unlink_style" bl_label = "Unlink Style" bl_options = {"REGISTER", "UNDO"} - material: bpy.props.StringProperty() - def execute(self, context): - self.file = IfcStore.get_file() - material = bpy.data.materials.get(self.material) - material.BIMMaterialProperties.ifc_style_id = 0 - if "Ifc" in material.name and "/" in material.name: - material.name = "/".join(material.name.split("/")[1:]) - return {"FINISHED"} + def _execute(self, context): + core.unlink_style(tool.Style, obj=context.active_object.active_material) -class EnableEditingStyle(bpy.types.Operator): +class EnableEditingStyle(bpy.types.Operator, Operator): bl_idname = "bim.enable_editing_style" bl_label = "Enable Editing Style" bl_options = {"REGISTER", "UNDO"} - material: bpy.props.StringProperty() - def execute(self, context): - material = bpy.data.materials.get(self.material) if self.material else context.active_object.active_material - props = material.BIMStyleProperties - props.attributes.clear() - - data = Data.styles[material.BIMMaterialProperties.ifc_style_id] - blenderbim.bim.helper.import_attributes("IfcSurfaceStyle", props.attributes, data) - props.is_editing_attributes = True - return {"FINISHED"} + def _execute(self, context): + core.enable_editing_style(tool.Style, obj=context.active_object.active_material) -class DisableEditingStyle(bpy.types.Operator): +class DisableEditingStyle(bpy.types.Operator, Operator): bl_idname = "bim.disable_editing_style" bl_options = {"REGISTER", "UNDO"} bl_label = "Disable Editing Style" - material: bpy.props.StringProperty() - def execute(self, context): - material = bpy.data.materials.get(self.material) if self.material else context.active_object.active_material - props = material.BIMStyleProperties - props.is_editing_attributes = False - return {"FINISHED"} + def _execute(self, context): + core.disable_editing_style(tool.Style, obj=context.active_object.active_material) -class EditStyle(bpy.types.Operator): +class EditStyle(bpy.types.Operator, Operator): bl_idname = "bim.edit_style" bl_label = "Edit Style" bl_options = {"REGISTER", "UNDO"} - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - material = context.active_object.active_material - props = material.BIMStyleProperties - attributes = blenderbim.bim.helper.export_attributes(props.attributes) - self.file = IfcStore.get_file() - style = self.file.by_id(material.BIMMaterialProperties.ifc_style_id) - ifcopenshell.api.run("style.edit_presentation_style", self.file, **{"style": style, "attributes": attributes}) - Data.load(IfcStore.get_file(), material.BIMMaterialProperties.ifc_style_id) - bpy.ops.bim.disable_editing_style() - return {"FINISHED"} + core.edit_style(tool.Ifc, tool.Style, obj=context.active_object.active_material) diff --git a/src/blenderbim/blenderbim/bim/module/style/prop.py b/src/blenderbim/blenderbim/bim/module/style/prop.py index 7b56fc86ee..b5ae846627 100644 --- a/src/blenderbim/blenderbim/bim/module/style/prop.py +++ b/src/blenderbim/blenderbim/bim/module/style/prop.py @@ -34,4 +34,4 @@ from bpy.props import ( class BIMStyleProperties(PropertyGroup): attributes: CollectionProperty(name="Attributes", type=Attribute) - is_editing_attributes: BoolProperty(name="Is Editing Attributes") + is_editing: BoolProperty(name="Is Editing") diff --git a/src/blenderbim/blenderbim/bim/module/style/ui.py b/src/blenderbim/blenderbim/bim/module/style/ui.py index 9da4296975..e1e91d064d 100644 --- a/src/blenderbim/blenderbim/bim/module/style/ui.py +++ b/src/blenderbim/blenderbim/bim/module/style/ui.py @@ -19,7 +19,7 @@ import blenderbim.bim.helper from bpy.types import Panel from blenderbim.bim.ifc import IfcStore -from ifcopenshell.api.style.data import Data +from blenderbim.bim.module.style.data import StyleAttributesData class BIM_PT_style(Panel): @@ -38,13 +38,12 @@ class BIM_PT_style(Panel): ) def draw(self, context): - material = context.active_object.active_material - props = material.BIMMaterialProperties + props = context.active_object.active_material.BIMMaterialProperties row = self.layout.row(align=True) if props.ifc_style_id: - row.operator("bim.update_style_colours", icon="GREASEPENCIL").material = material.name - row.operator("bim.unlink_style", icon="UNLINKED", text="").material = material.name - row.operator("bim.remove_style", icon="X", text="").material = material.name + row.operator("bim.update_style_colours", icon="GREASEPENCIL") + row.operator("bim.unlink_style", icon="UNLINKED", text="") + row.operator("bim.remove_style", icon="X", text="") else: row.operator("bim.add_style", icon="ADD") @@ -66,12 +65,13 @@ class BIM_PT_style_attributes(Panel): return False def draw(self, context): + if not StyleAttributesData.is_loaded: + StyleAttributesData.load() + obj = context.active_object.active_material mprops = obj.BIMMaterialProperties props = obj.BIMStyleProperties - if mprops.ifc_style_id not in Data.styles: - Data.load(IfcStore.get_file(), mprops.ifc_style_id) - if props.is_editing_attributes: + if props.is_editing: row = self.layout.row(align=True) row.operator("bim.edit_style", icon="CHECKMARK") row.operator("bim.disable_editing_style", icon="CANCEL", text="") @@ -84,9 +84,7 @@ class BIM_PT_style_attributes(Panel): row.label(text="STEP ID") row.label(text=str(mprops.ifc_style_id)) - for name, value in Data.styles[mprops.ifc_style_id].items(): - if name in ["id", "type", "Styles"]: - continue + for attribute in StyleAttributesData.data["attributes"]: row = self.layout.row(align=True) - row.label(text=name) - row.label(text=str(value)) + row.label(text=attribute["name"]) + row.label(text=attribute["value"]) diff --git a/src/blenderbim/blenderbim/core/aggregate.py b/src/blenderbim/blenderbim/core/aggregate.py index e1eda02454..b57aa52335 100644 --- a/src/blenderbim/blenderbim/core/aggregate.py +++ b/src/blenderbim/blenderbim/core/aggregate.py @@ -1,3 +1,22 @@ +# 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 enable_editing_aggregate(aggregator, obj=None): aggregator.enable_editing(obj) diff --git a/src/blenderbim/blenderbim/core/context.py b/src/blenderbim/blenderbim/core/context.py index 0f30476726..ba4c15b4c8 100644 --- a/src/blenderbim/blenderbim/core/context.py +++ b/src/blenderbim/blenderbim/core/context.py @@ -31,15 +31,15 @@ def remove_context(ifc, context=None): ifc.run("context.remove_context", context=context) -def enable_editing_context(context_editor, context=None): - context_editor.set_context(context) - context_editor.import_attributes() +def enable_editing_context(context_tool, context=None): + context_tool.set_context(context) + context_tool.import_attributes() -def disable_editing_context(context_editor): - context_editor.clear_context() +def disable_editing_context(context): + context.clear_context() -def edit_context(ifc, context_editor): - ifc.run("context.edit_context", context=context_editor.get_context(), attributes=context_editor.export_attributes()) - disable_editing_context(context_editor) +def edit_context(ifc, context): + ifc.run("context.edit_context", context=context.get_context(), attributes=context.export_attributes()) + disable_editing_context(context) diff --git a/src/blenderbim/blenderbim/core/owner.py b/src/blenderbim/blenderbim/core/owner.py index 0c31a7cad6..fb620de619 100644 --- a/src/blenderbim/blenderbim/core/owner.py +++ b/src/blenderbim/blenderbim/core/owner.py @@ -25,26 +25,26 @@ def remove_person(ifc, person=None): ifc.run("owner.remove_person", person=person) -def enable_editing_person(person_editor, person=None): - person_editor.set_person(person) - person_editor.import_attributes() +def enable_editing_person(owner, person=None): + owner.set_person(person) + owner.import_person_attributes() -def disable_editing_person(person_editor): - person_editor.clear_person() +def disable_editing_person(owner): + owner.clear_person() -def edit_person(ifc, person_editor): - ifc.run("owner.edit_person", person=person_editor.get_person(), attributes=person_editor.export_attributes()) - disable_editing_person(person_editor) +def edit_person(ifc, owner): + ifc.run("owner.edit_person", person=owner.get_person(), attributes=owner.export_person_attributes()) + disable_editing_person(owner) -def add_person_attribute(person_editor, name=None): - person_editor.add_attribute(name) +def add_person_attribute(owner, name=None): + owner.add_person_attribute(name) -def remove_person_attribute(person_editor, name=None, id=None): - person_editor.remove_attribute(name, id) +def remove_person_attribute(owner, name=None, id=None): + owner.remove_person_attribute(name, id) def add_role(ifc, parent=None): @@ -55,18 +55,18 @@ def remove_role(ifc, role=None): ifc.run("owner.remove_role", role=role) -def enable_editing_role(role_editor, role=None): - role_editor.set_role(role) - role_editor.import_attributes() +def enable_editing_role(owner, role=None): + owner.set_role(role) + owner.import_role_attributes() -def disable_editing_role(role_editor): - role_editor.clear_role() +def disable_editing_role(owner): + owner.clear_role() -def edit_role(ifc, role_editor): - ifc.run("owner.edit_role", role=role_editor.get_role(), attributes=role_editor.export_attributes()) - role_editor.clear_role() +def edit_role(ifc, owner): + ifc.run("owner.edit_role", role=owner.get_role(), attributes=owner.export_role_attributes()) + owner.clear_role() def add_address(ifc, parent=None, ifc_class="IfcPostalAddress"): @@ -77,27 +77,27 @@ def remove_address(ifc, address=None): ifc.run("owner.remove_address", address=address) -def enable_editing_address(address_editor, address=None): - address_editor.set_address(address) - address_editor.import_attributes() +def enable_editing_address(owner, address=None): + owner.set_address(address) + owner.import_address_attributes() -def disable_editing_address(address_editor): - address_editor.clear_address() +def disable_editing_address(owner): + owner.clear_address() -def edit_address(ifc, address_editor): - address = address_editor.get_address() - ifc.run("owner.edit_address", address=address, attributes=address_editor.export_attributes()) - address_editor.clear_address() +def edit_address(ifc, owner): + address = owner.get_address() + ifc.run("owner.edit_address", address=address, attributes=owner.export_address_attributes()) + owner.clear_address() -def add_address_attribute(address_editor, name=None): - address_editor.add_attribute(name) +def add_address_attribute(owner, name=None): + owner.add_address_attribute(name) -def remove_address_attribute(address_editor, name=None, id=None): - address_editor.remove_attribute(name, id) +def remove_address_attribute(owner, name=None, id=None): + owner.remove_address_attribute(name, id) def add_organisation(ifc): @@ -108,19 +108,19 @@ def remove_organisation(ifc, organisation=None): ifc.run("owner.remove_organisation", organisation=organisation) -def enable_editing_organisation(organisation_editor, organisation=None): - organisation_editor.set_organisation(organisation) - organisation_editor.import_attributes() +def enable_editing_organisation(owner, organisation=None): + owner.set_organisation(organisation) + owner.import_organisation_attributes() -def disable_editing_organisation(organisation_editor): - organisation_editor.clear_organisation() +def disable_editing_organisation(owner): + owner.clear_organisation() -def edit_organisation(ifc, organisation_editor): - organisation = organisation_editor.get_organisation() - ifc.run("owner.edit_organisation", organisation=organisation, attributes=organisation_editor.export_attributes()) - organisation_editor.clear_organisation() +def edit_organisation(ifc, owner): + organisation = owner.get_organisation() + ifc.run("owner.edit_organisation", organisation=organisation, attributes=owner.export_organisation_attributes()) + owner.clear_organisation() def add_person_and_organisation(ifc, person=None, organisation=None): diff --git a/src/blenderbim/blenderbim/core/style.py b/src/blenderbim/blenderbim/core/style.py new file mode 100644 index 0000000000..6369034106 --- /dev/null +++ b/src/blenderbim/blenderbim/core/style.py @@ -0,0 +1,69 @@ +# 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 add_style(ifc, style, obj=None): + element = ifc.run("style.add_style", name=style.get_name(obj)) + style.link(element, obj) + ifc.run( + "style.add_surface_style", + style=element, + ifc_class="IfcSurfaceStyleRendering", + attributes=style.get_surface_rendering_attributes(obj), + ) + material = ifc.get_entity(obj) + if material: + ifc.run("style.assign_material_style", material=material, style=element, context=style.get_context(obj)) + return element + + +def remove_style(ifc, style, obj=None): + ifc.run("style.remove_style", style=style.get_style(obj)) + style.unlink(obj=obj) + + +def update_style_colours(ifc, style, obj=None): + rendering_style = style.get_surface_rendering_style(obj) + if rendering_style: + attributes = style.get_surface_rendering_attributes(obj) + ifc.run("style.edit_surface_style", style=rendering_style, attributes=attributes) + return + + shading_style = style.get_surface_shading_style(obj) + if shading_style: + attributes = style.get_surface_shading_attributes(obj) + ifc.run("style.edit_surface_style", style=shading_style, attributes=attributes) + + +def unlink_style(style, obj=None): + style.unlink(obj) + + +def enable_editing_style(style, obj=None): + style.enable_editing(obj) + style.import_surface_attributes(style.get_style(obj), obj) + + +def disable_editing_style(style, obj=None): + style.disable_editing(obj) + + +def edit_style(ifc, style, obj=None): + attributes = style.export_surface_attributes(obj) + ifc.run("style.edit_presentation_style", style=style.get_style(obj), attributes=attributes) + style.disable_editing(obj) diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index a4ce6931c8..c471407a57 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -17,273 +17,122 @@ # along with BlenderBIM Add-on. If not, see . import abc +import inspect + +# fmt: off +# pylint: skip-file + +# This interface class and decorator is magic syntatic sugar to allow concise interface definitions +# If we didn't do this, Python is unnecessarily verbose, which I find distracting. Don't black this file :) +class Interface(abc.ABC): pass +def interface(cls): + attrs = {n: classmethod(abc.abstractmethod(f)) for n, f in inspect.getmembers(cls, predicate=inspect.isfunction)} + return type(cls.__name__, (Interface, cls), attrs) -class AddressEditor(abc.ABC): - @classmethod - @abc.abstractmethod - def set_address(cls, address): - pass - - @classmethod - @abc.abstractmethod - def import_attributes(cls): - pass - - @classmethod - @abc.abstractmethod - def clear_address(cls): - pass - - @classmethod - @abc.abstractmethod - def get_address(cls): - pass - - @classmethod - @abc.abstractmethod - def export_attributes(cls): - pass - - @classmethod - @abc.abstractmethod - def add_attribute(cls, name): - pass - - @classmethod - @abc.abstractmethod - def remove_attribute(cls, name, id): - pass +@interface +class Aggregate: + def enable_editing(cls, obj): pass + def disable_editing(cls, obj): pass + def can_aggregate(cls, relating_object, related_object): pass -class Aggregator(abc.ABC): - @classmethod - @abc.abstractmethod - def enable_editing(cls, obj): - pass - - @classmethod - @abc.abstractmethod - def disable_editing(cls, obj): - pass - - @classmethod - @abc.abstractmethod - def can_aggregate(cls, relating_object, related_object): - pass +@interface +class Blender: pass -class Blender(abc.ABC): - pass +@interface +class Collector: + def assign(cls, obj): pass -class Collector(abc.ABC): - @classmethod - @abc.abstractmethod - def assign(cls, obj): - pass +@interface +class Container: + def can_contain(cls, structure_obj, element_obj): pass + def enable_editing(cls, obj): pass + def disable_editing(cls, obj): pass + def import_containers(cls, parent=None): pass -class Container(abc.ABC): - @classmethod - @abc.abstractmethod - def can_contain(cls, structure_obj, element_obj): - pass - - @classmethod - @abc.abstractmethod - def enable_editing(cls, obj): - pass - - @classmethod - @abc.abstractmethod - def disable_editing(cls, obj): - pass - - @classmethod - @abc.abstractmethod - def import_containers(cls, parent=None): - pass +@interface +class Context: + def set_context(cls, context): pass + def import_attributes(cls): pass + def clear_context(cls): pass + def get_context(cls): pass + def export_attributes(cls): pass -class ContextEditor(abc.ABC): - @classmethod - @abc.abstractmethod - def set_context(cls, context): - pass - - @classmethod - @abc.abstractmethod - def import_attributes(cls): - pass - - @classmethod - @abc.abstractmethod - def clear_context(cls): - pass - - @classmethod - @abc.abstractmethod - def get_context(cls): - pass - - @classmethod - @abc.abstractmethod - def export_attributes(cls): - pass +@interface +class Ifc: + def run(cls, command, **kwargs): pass + def get(cls): pass + def get_entity(cls, obj): pass + def link(cls, element, obj): pass + def unlink(cls, element=None, obj=None): pass -class Ifc(abc.ABC): - @classmethod - @abc.abstractmethod - def run(cls, command, **kwargs): - pass - - @classmethod - @abc.abstractmethod - def get_entity(cls, obj): - pass +@interface +class Owner: + def set_user(cls, user): pass + def get_user(cls): pass + def clear_user(cls): pass + def add_address_attribute(cls, name): pass + def clear_address(cls): pass + def export_address_attributes(cls): pass + def get_address(cls): pass + def import_address_attributes(cls): pass + def remove_address_attribute(cls, name, id): pass + def set_address(cls, address): pass + def set_organisation(cls, organisation): pass + def import_organisation_attributes(cls): pass + def clear_organisation(cls): pass + def get_organisation(cls): pass + def export_organisation_attributes(cls): pass + def set_person(cls, person): pass + def import_person_attributes(cls): pass + def clear_person(cls): pass + def export_person_attributes(cls): pass + def get_person(cls): pass + def add_person_attribute(cls, name): pass + def remove_person_attribute(cls, name, id): pass + def set_role(cls, role): pass + def import_role_attributes(cls): pass + def clear_role(cls): pass + def get_role(cls): pass + def export_role_attributes(cls): pass -class OrganisationEditor(abc.ABC): - @classmethod - @abc.abstractmethod - def set_organisation(cls, organisation): - pass - - @classmethod - @abc.abstractmethod - def import_attributes(cls): - pass - - @classmethod - @abc.abstractmethod - def clear_organisation(cls): - pass - - @classmethod - @abc.abstractmethod - def get_organisation(cls): - pass - - @classmethod - @abc.abstractmethod - def export_attributes(cls): - pass +@interface +class Selector: + def set_active(cls, obj): pass -class Owner(abc.ABC): - @classmethod - @abc.abstractmethod - def set_user(cls, user): - pass - - @classmethod - @abc.abstractmethod - def get_user(cls): - pass - - @classmethod - @abc.abstractmethod - def clear_user(cls): - pass +@interface +class Style: + def disable_editing(cls, obj): pass + def enable_editing(cls, obj): pass + def export_surface_attributes(cls, obj): pass + def get_context(cls, obj): pass + def get_name(cls, obj): pass + def get_style(cls, obj): pass + def get_surface_rendering_attributes(cls, obj): pass + def get_surface_rendering_style(cls, obj): pass + def get_surface_shading_attributes(cls, obj): pass + def get_surface_shading_style(cls, obj): pass + def import_surface_attributes(cls, style, obj): pass + def link(cls, style, obj): pass + def unlink(cls, obj): pass -class PersonEditor(abc.ABC): - @classmethod - @abc.abstractmethod - def set_person(cls, person): - pass - - @classmethod - @abc.abstractmethod - def import_attributes(cls): - pass - - @classmethod - @abc.abstractmethod - def clear_person(cls): - pass - - @classmethod - @abc.abstractmethod - def export_attributes(cls): - pass - - @classmethod - @abc.abstractmethod - def get_person(cls): - pass - - @classmethod - @abc.abstractmethod - def add_attribute(cls, name): - pass - - @classmethod - @abc.abstractmethod - def remove_attribute(cls, name, id): - pass +@interface +class Surveyor: + def get_absolute_matrix(cls, obj): pass -class RoleEditor(abc.ABC): - @classmethod - @abc.abstractmethod - def set_role(cls, role): - pass - - @classmethod - @abc.abstractmethod - def import_attributes(cls): - pass - - @classmethod - @abc.abstractmethod - def clear_role(cls): - pass - - @classmethod - @abc.abstractmethod - def get_role(cls): - pass - - @classmethod - @abc.abstractmethod - def export_attributes(cls): - pass - - -class Selector(abc.ABC): - @classmethod - @abc.abstractmethod - def set_active(cls, obj): - pass - - -class Surveyor(abc.ABC): - @classmethod - @abc.abstractmethod - def get_absolute_matrix(cls, obj): - pass - - -class Voider(abc.ABC): - @classmethod - @abc.abstractmethod - def can_void(cls, opening, element): - pass - - @classmethod - @abc.abstractmethod - def void(cls, opening_obj, building_obj): - pass - - @classmethod - @abc.abstractmethod - def unvoid(cls, opening_obj): - pass - - @classmethod - @abc.abstractmethod - def set_void_display(cls, opening_obj): - pass +@interface +class Voider: + def can_void(cls, opening, element): pass + def void(cls, opening_obj, building_obj): pass + def unvoid(cls, opening_obj): pass + def set_void_display(cls, opening_obj): pass diff --git a/src/blenderbim/blenderbim/tool/__init__.py b/src/blenderbim/blenderbim/tool/__init__.py index 2b081b3416..16719d31a2 100644 --- a/src/blenderbim/blenderbim/tool/__init__.py +++ b/src/blenderbim/blenderbim/tool/__init__.py @@ -16,15 +16,12 @@ # You should have received a copy of the GNU General Public License # along with BlenderBIM Add-on. If not, see . -from blenderbim.tool.address_editor import AddressEditor -from blenderbim.tool.aggregator import Aggregator +from blenderbim.tool.aggregate import Aggregate from blenderbim.tool.blender import Blender from blenderbim.tool.collector import Collector from blenderbim.tool.container import Container -from blenderbim.tool.context_editor import ContextEditor +from blenderbim.tool.context import Context from blenderbim.tool.ifc import Ifc -from blenderbim.tool.organisation_editor import OrganisationEditor from blenderbim.tool.owner import Owner -from blenderbim.tool.person_editor import PersonEditor -from blenderbim.tool.role_editor import RoleEditor +from blenderbim.tool.style import Style from blenderbim.tool.surveyor import Surveyor diff --git a/src/blenderbim/blenderbim/tool/address_editor.py b/src/blenderbim/blenderbim/tool/address_editor.py deleted file mode 100644 index 0547666251..0000000000 --- a/src/blenderbim/blenderbim/tool/address_editor.py +++ /dev/null @@ -1,108 +0,0 @@ -# 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 blenderbim.core.tool -import blenderbim.bim.helper -import blenderbim.tool as tool - - -class AddressEditor(blenderbim.core.tool.AddressEditor): - @classmethod - def set_address(cls, address): - bpy.context.scene.BIMOwnerProperties.active_address_id = address.id() - - @classmethod - def import_attributes(cls): - props = bpy.context.scene.BIMOwnerProperties - props.address_attributes.clear() - props.address_lines.clear() - props.telephone_numbers.clear() - props.facsimile_numbers.clear() - props.electronic_mail_addresses.clear() - props.messaging_ids.clear() - - address = cls.get_address() - - def callback(name, prop, data): - if name == "AddressLines": - for line in data[name] or []: - props.address_lines.add().name = line - elif name == "TelephoneNumbers": - for line in data[name] or []: - props.telephone_numbers.add().name = line - elif name == "FacsimileNumbers": - for line in data[name] or []: - props.facsimile_numbers.add().name = line - elif name == "ElectronicMailAddresses": - for line in data[name] or []: - props.electronic_mail_addresses.add().name = line - elif name == "MessagingIDs": - for line in data[name] or []: - props.messaging_ids.add().name = line - - blenderbim.bim.helper.import_attributes(address.is_a(), props.address_attributes, address.get_info(), callback) - - @classmethod - def clear_address(cls): - bpy.context.scene.BIMOwnerProperties.active_address_id = 0 - - @classmethod - def get_address(cls): - return tool.Ifc().get().by_id(bpy.context.scene.BIMOwnerProperties.active_address_id) - - @classmethod - def export_attributes(cls): - props = bpy.context.scene.BIMOwnerProperties - attributes = blenderbim.bim.helper.export_attributes(props.address_attributes) - if cls.get_address().is_a("IfcPostalAddress"): - attributes["AddressLines"] = [l.name for l in props.address_lines] or None - elif cls.get_address().is_a("IfcTelecomAddress"): - attributes["TelephoneNumbers"] = [l.name for l in props.telephone_numbers] or None - attributes["FacsimileNumbers"] = [l.name for l in props.facsimile_numbers] or None - attributes["ElectronicMailAddresses"] = [l.name for l in props.electronic_mail_addresses] or None - attributes["MessagingIDs"] = [l.name for l in props.messaging_ids] or None - return attributes - - @classmethod - def add_attribute(cls, name): - props = bpy.context.scene.BIMOwnerProperties - if name == "AddressLines": - props.address_lines.add() - elif name == "TelephoneNumbers": - props.telephone_numbers.add() - elif name == "FacsimileNumbers": - props.facsimile_numbers.add() - elif name == "ElectronicMailAddresses": - props.electronic_mail_addresses.add() - elif name == "MessagingIDs": - props.messaging_ids.add() - - @classmethod - def remove_attribute(cls, name, id): - props = bpy.context.scene.BIMOwnerProperties - if name == "AddressLines": - props.address_lines.remove(id) - elif name == "TelephoneNumbers": - props.telephone_numbers.remove(id) - elif name == "FacsimileNumbers": - props.facsimile_numbers.remove(id) - elif name == "ElectronicMailAddresses": - props.electronic_mail_addresses.remove(id) - elif name == "MessagingIDs": - props.messaging_ids.remove(id) diff --git a/src/blenderbim/blenderbim/tool/aggregator.py b/src/blenderbim/blenderbim/tool/aggregate.py similarity index 61% rename from src/blenderbim/blenderbim/tool/aggregator.py rename to src/blenderbim/blenderbim/tool/aggregate.py index a114b33adb..b1d45f4d69 100644 --- a/src/blenderbim/blenderbim/tool/aggregator.py +++ b/src/blenderbim/blenderbim/tool/aggregate.py @@ -1,8 +1,26 @@ +# 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.tool import blenderbim.tool as tool -class Aggregator(blenderbim.core.tool.Aggregator): +class Aggregate(blenderbim.core.tool.Aggregate): @classmethod def enable_editing(cls, obj): obj.BIMObjectAggregateProperties.is_editing = True diff --git a/src/blenderbim/blenderbim/tool/context_editor.py b/src/blenderbim/blenderbim/tool/context.py similarity index 97% rename from src/blenderbim/blenderbim/tool/context_editor.py rename to src/blenderbim/blenderbim/tool/context.py index 0414d09ede..6eeebf22e2 100644 --- a/src/blenderbim/blenderbim/tool/context_editor.py +++ b/src/blenderbim/blenderbim/tool/context.py @@ -22,7 +22,7 @@ import blenderbim.tool as tool import blenderbim.core.tool -class ContextEditor(blenderbim.core.tool.ContextEditor): +class Context(blenderbim.core.tool.Context): @classmethod def set_context(cls, context): bpy.context.scene.BIMContextProperties.active_context_id = context.id() diff --git a/src/blenderbim/blenderbim/tool/organisation_editor.py b/src/blenderbim/blenderbim/tool/organisation_editor.py deleted file mode 100644 index 8158b0fcdf..0000000000 --- a/src/blenderbim/blenderbim/tool/organisation_editor.py +++ /dev/null @@ -1,53 +0,0 @@ -# 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.api -import blenderbim.core.tool -import blenderbim.bim.helper -import blenderbim.tool as tool - - -class OrganisationEditor(blenderbim.core.tool.OrganisationEditor): - @classmethod - def set_organisation(cls, organisation): - bpy.context.scene.BIMOwnerProperties.active_organisation_id = organisation.id() - - @classmethod - def import_attributes(cls): - organisation = tool.Ifc.get().by_id(bpy.context.scene.BIMOwnerProperties.active_organisation_id) - props = bpy.context.scene.BIMOwnerProperties - props.organisation_attributes.clear() - - blenderbim.bim.helper.import_attributes( - "IfcOrganization", props.organisation_attributes, organisation.get_info() - ) - - @classmethod - def clear_organisation(cls): - bpy.context.scene.BIMOwnerProperties.active_organisation_id = 0 - - @classmethod - def export_attributes(cls): - props = bpy.context.scene.BIMOwnerProperties - attributes = blenderbim.bim.helper.export_attributes(props.organisation_attributes) - return attributes - - @classmethod - def get_organisation(cls): - return tool.Ifc().get().by_id(bpy.context.scene.BIMOwnerProperties.active_organisation_id) diff --git a/src/blenderbim/blenderbim/tool/owner.py b/src/blenderbim/blenderbim/tool/owner.py index 65ba74bf87..3977bceacc 100644 --- a/src/blenderbim/blenderbim/tool/owner.py +++ b/src/blenderbim/blenderbim/tool/owner.py @@ -34,3 +34,199 @@ class Owner(blenderbim.core.tool.Owner): @classmethod def clear_user(cls): bpy.context.scene.BIMOwnerProperties.active_user_id = 0 + + @classmethod + def set_address(cls, address): + bpy.context.scene.BIMOwnerProperties.active_address_id = address.id() + + @classmethod + def import_address_attributes(cls): + props = bpy.context.scene.BIMOwnerProperties + props.address_attributes.clear() + props.address_lines.clear() + props.telephone_numbers.clear() + props.facsimile_numbers.clear() + props.electronic_mail_addresses.clear() + props.messaging_ids.clear() + + address = cls.get_address() + + def callback(name, prop, data): + if name == "AddressLines": + for line in data[name] or []: + props.address_lines.add().name = line + elif name == "TelephoneNumbers": + for line in data[name] or []: + props.telephone_numbers.add().name = line + elif name == "FacsimileNumbers": + for line in data[name] or []: + props.facsimile_numbers.add().name = line + elif name == "ElectronicMailAddresses": + for line in data[name] or []: + props.electronic_mail_addresses.add().name = line + elif name == "MessagingIDs": + for line in data[name] or []: + props.messaging_ids.add().name = line + + blenderbim.bim.helper.import_attributes(address.is_a(), props.address_attributes, address.get_info(), callback) + + @classmethod + def clear_address(cls): + bpy.context.scene.BIMOwnerProperties.active_address_id = 0 + + @classmethod + def get_address(cls): + return tool.Ifc().get().by_id(bpy.context.scene.BIMOwnerProperties.active_address_id) + + @classmethod + def export_address_attributes(cls): + props = bpy.context.scene.BIMOwnerProperties + attributes = blenderbim.bim.helper.export_attributes(props.address_attributes) + if cls.get_address().is_a("IfcPostalAddress"): + attributes["AddressLines"] = [l.name for l in props.address_lines] or None + elif cls.get_address().is_a("IfcTelecomAddress"): + attributes["TelephoneNumbers"] = [l.name for l in props.telephone_numbers] or None + attributes["FacsimileNumbers"] = [l.name for l in props.facsimile_numbers] or None + attributes["ElectronicMailAddresses"] = [l.name for l in props.electronic_mail_addresses] or None + attributes["MessagingIDs"] = [l.name for l in props.messaging_ids] or None + return attributes + + @classmethod + def add_address_attribute(cls, name): + props = bpy.context.scene.BIMOwnerProperties + if name == "AddressLines": + props.address_lines.add() + elif name == "TelephoneNumbers": + props.telephone_numbers.add() + elif name == "FacsimileNumbers": + props.facsimile_numbers.add() + elif name == "ElectronicMailAddresses": + props.electronic_mail_addresses.add() + elif name == "MessagingIDs": + props.messaging_ids.add() + + @classmethod + def remove_address_attribute(cls, name, id): + props = bpy.context.scene.BIMOwnerProperties + if name == "AddressLines": + props.address_lines.remove(id) + elif name == "TelephoneNumbers": + props.telephone_numbers.remove(id) + elif name == "FacsimileNumbers": + props.facsimile_numbers.remove(id) + elif name == "ElectronicMailAddresses": + props.electronic_mail_addresses.remove(id) + elif name == "MessagingIDs": + props.messaging_ids.remove(id) + + @classmethod + def set_organisation(cls, organisation): + bpy.context.scene.BIMOwnerProperties.active_organisation_id = organisation.id() + + @classmethod + def import_organisation_attributes(cls): + organisation = tool.Ifc.get().by_id(bpy.context.scene.BIMOwnerProperties.active_organisation_id) + props = bpy.context.scene.BIMOwnerProperties + props.organisation_attributes.clear() + + blenderbim.bim.helper.import_attributes( + "IfcOrganization", props.organisation_attributes, organisation.get_info() + ) + + @classmethod + def clear_organisation(cls): + bpy.context.scene.BIMOwnerProperties.active_organisation_id = 0 + + @classmethod + def export_organisation_attributes(cls): + props = bpy.context.scene.BIMOwnerProperties + attributes = blenderbim.bim.helper.export_attributes(props.organisation_attributes) + return attributes + + @classmethod + def get_organisation(cls): + return tool.Ifc().get().by_id(bpy.context.scene.BIMOwnerProperties.active_organisation_id) + + @classmethod + def set_person(cls, person): + bpy.context.scene.BIMOwnerProperties.active_person_id = person.id() + + @classmethod + def import_person_attributes(cls): + person = tool.Ifc.get().by_id(bpy.context.scene.BIMOwnerProperties.active_person_id) + props = bpy.context.scene.BIMOwnerProperties + props.person_attributes.clear() + props.middle_names.clear() + props.prefix_titles.clear() + props.suffix_titles.clear() + + def callback(name, prop, data): + if name == "MiddleNames": + for name in data["MiddleNames"] or []: + props.middle_names.add().name = name or "" + if name == "PrefixTitles": + for name in data["PrefixTitles"] or []: + props.prefix_titles.add().name = name or "" + if name == "SuffixTitles": + for name in data["SuffixTitles"] or []: + props.suffix_titles.add().name = name or "" + + blenderbim.bim.helper.import_attributes("IfcPerson", props.person_attributes, person.get_info(), callback) + + @classmethod + def clear_person(cls): + bpy.context.scene.BIMOwnerProperties.active_person_id = 0 + + @classmethod + def export_person_attributes(cls): + props = bpy.context.scene.BIMOwnerProperties + attributes = blenderbim.bim.helper.export_attributes(props.person_attributes) + attributes["MiddleNames"] = [v.name for v in props.middle_names] if props.middle_names else None + attributes["PrefixTitles"] = [v.name for v in props.prefix_titles] if props.prefix_titles else None + attributes["SuffixTitles"] = [v.name for v in props.suffix_titles] if props.suffix_titles else None + return attributes + + @classmethod + def get_person(cls): + return tool.Ifc().get().by_id(bpy.context.scene.BIMOwnerProperties.active_person_id) + + @classmethod + def add_person_attribute(cls, name): + if name == "MiddleNames": + bpy.context.scene.BIMOwnerProperties.middle_names.add() + elif name == "PrefixTitles": + bpy.context.scene.BIMOwnerProperties.prefix_titles.add() + elif name == "SuffixTitles": + bpy.context.scene.BIMOwnerProperties.suffix_titles.add() + + @classmethod + def remove_person_attribute(cls, name, id): + if name == "MiddleNames": + bpy.context.scene.BIMOwnerProperties.middle_names.remove(id) + elif name == "PrefixTitles": + bpy.context.scene.BIMOwnerProperties.prefix_titles.remove(id) + elif name == "SuffixTitles": + bpy.context.scene.BIMOwnerProperties.suffix_titles.remove(id) + + @classmethod + def set_role(cls, role): + bpy.context.scene.BIMOwnerProperties.active_role_id = role.id() + + @classmethod + def import_role_attributes(cls): + role = cls.get_role() + props = bpy.context.scene.BIMOwnerProperties + props.role_attributes.clear() + blenderbim.bim.helper.import_attributes("IfcActorRole", props.role_attributes, role.get_info()) + + @classmethod + def clear_role(cls): + bpy.context.scene.BIMOwnerProperties.active_role_id = 0 + + @classmethod + def get_role(cls): + return tool.Ifc().get().by_id(bpy.context.scene.BIMOwnerProperties.active_role_id) + + @classmethod + def export_role_attributes(cls): + return blenderbim.bim.helper.export_attributes(bpy.context.scene.BIMOwnerProperties.role_attributes) diff --git a/src/blenderbim/blenderbim/tool/person_editor.py b/src/blenderbim/blenderbim/tool/person_editor.py deleted file mode 100644 index 7acacab0e2..0000000000 --- a/src/blenderbim/blenderbim/tool/person_editor.py +++ /dev/null @@ -1,86 +0,0 @@ -# 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.api -import blenderbim.core.tool -import blenderbim.bim.helper -import blenderbim.tool as tool - - -class PersonEditor(blenderbim.core.tool.PersonEditor): - @classmethod - def set_person(cls, person): - bpy.context.scene.BIMOwnerProperties.active_person_id = person.id() - - @classmethod - def import_attributes(cls): - person = tool.Ifc.get().by_id(bpy.context.scene.BIMOwnerProperties.active_person_id) - props = bpy.context.scene.BIMOwnerProperties - props.person_attributes.clear() - props.middle_names.clear() - props.prefix_titles.clear() - props.suffix_titles.clear() - - def callback(name, prop, data): - if name == "MiddleNames": - for name in data["MiddleNames"] or []: - props.middle_names.add().name = name or "" - if name == "PrefixTitles": - for name in data["PrefixTitles"] or []: - props.prefix_titles.add().name = name or "" - if name == "SuffixTitles": - for name in data["SuffixTitles"] or []: - props.suffix_titles.add().name = name or "" - - blenderbim.bim.helper.import_attributes("IfcPerson", props.person_attributes, person.get_info(), callback) - - @classmethod - def clear_person(cls): - bpy.context.scene.BIMOwnerProperties.active_person_id = 0 - - @classmethod - def export_attributes(cls): - props = bpy.context.scene.BIMOwnerProperties - attributes = blenderbim.bim.helper.export_attributes(props.person_attributes) - attributes["MiddleNames"] = [v.name for v in props.middle_names] if props.middle_names else None - attributes["PrefixTitles"] = [v.name for v in props.prefix_titles] if props.prefix_titles else None - attributes["SuffixTitles"] = [v.name for v in props.suffix_titles] if props.suffix_titles else None - return attributes - - @classmethod - def get_person(cls): - return tool.Ifc().get().by_id(bpy.context.scene.BIMOwnerProperties.active_person_id) - - @classmethod - def add_attribute(cls, name): - if name == "MiddleNames": - bpy.context.scene.BIMOwnerProperties.middle_names.add() - elif name == "PrefixTitles": - bpy.context.scene.BIMOwnerProperties.prefix_titles.add() - elif name == "SuffixTitles": - bpy.context.scene.BIMOwnerProperties.suffix_titles.add() - - @classmethod - def remove_attribute(cls, name, id): - if name == "MiddleNames": - bpy.context.scene.BIMOwnerProperties.middle_names.remove(id) - elif name == "PrefixTitles": - bpy.context.scene.BIMOwnerProperties.prefix_titles.remove(id) - elif name == "SuffixTitles": - bpy.context.scene.BIMOwnerProperties.suffix_titles.remove(id) diff --git a/src/blenderbim/blenderbim/tool/style.py b/src/blenderbim/blenderbim/tool/style.py new file mode 100644 index 0000000000..0ddac3d0a5 --- /dev/null +++ b/src/blenderbim/blenderbim/tool/style.py @@ -0,0 +1,114 @@ +# 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 ifcopenshell +import blenderbim.core.tool +import blenderbim.tool as tool +import blenderbim.bim.helper + + +class Style(blenderbim.core.tool.Style): + @classmethod + def disable_editing(cls, obj): + obj.BIMStyleProperties.is_editing = False + + @classmethod + def enable_editing(cls, obj): + obj.BIMStyleProperties.is_editing = True + + @classmethod + def export_surface_attributes(cls, obj): + return blenderbim.bim.helper.export_attributes(obj.BIMStyleProperties.attributes) + + @classmethod + def get_context(cls, obj): + return ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW") + + @classmethod + def get_name(cls, obj): + return obj.name + + @classmethod + def get_style(cls, obj): + if obj.BIMMaterialProperties.ifc_style_id: + return tool.Ifc.get().by_id(obj.BIMMaterialProperties.ifc_style_id) + + @classmethod + def get_surface_rendering_attributes(cls, obj): + transparency = obj.diffuse_color[3] + diffuse_colour = obj.diffuse_color + if obj.use_nodes and hasattr(obj.node_tree, "nodes") and "Principled BSDF" in obj.node_tree.nodes: + bsdf = obj.node_tree.nodes["Principled BSDF"] + transparency = bsdf.inputs["Alpha"].default_value + diffuse_colour = bsdf.inputs["Base Color"].default_value + transparency = 1 - transparency + return { + "SurfaceColour": { + "Name": None, + "Red": obj.diffuse_color[0], + "Green": obj.diffuse_color[1], + "Blue": obj.diffuse_color[2], + }, + "Transparency": transparency, + "DiffuseColour": { + "Name": None, + "Red": obj.diffuse_color[0], + "Green": obj.diffuse_color[1], + "Blue": obj.diffuse_color[2], + }, + } + + @classmethod + def get_surface_rendering_style(cls, obj): + if obj.BIMMaterialProperties.ifc_style_id: + style = tool.Ifc.get().by_id(obj.BIMMaterialProperties.ifc_style_id) + items = [s for s in style.Styles if s.is_a("IfcSurfaceStyleRendering")] + if items: + return items[0] + + @classmethod + def get_surface_shading_attributes(cls, obj): + return { + "SurfaceColour": { + "Name": None, + "Red": obj.diffuse_color[0], + "Green": obj.diffuse_color[1], + "Blue": obj.diffuse_color[2], + }, + "Transparency": 1 - obj.diffuse_color[3], + } + + @classmethod + def get_surface_shading_style(cls, obj): + if obj.BIMMaterialProperties.ifc_style_id: + style = tool.Ifc.get().by_id(obj.BIMMaterialProperties.ifc_style_id) + items = [s for s in style.Styles if s.is_a("IfcSurfaceStyleShading")] + if items: + return items[0] + + @classmethod + def import_surface_attributes(cls, style, obj): + blenderbim.bim.helper.import_attributes2(style, obj.BIMStyleProperties.attributes) + + @classmethod + def link(cls, style, obj): + obj.BIMMaterialProperties.ifc_style_id = style.id() + + @classmethod + def unlink(self, obj): + obj.BIMMaterialProperties.ifc_style_id = 0 diff --git a/src/blenderbim/test/bim/feature/style.feature b/src/blenderbim/test/bim/feature/style.feature new file mode 100644 index 0000000000..b8fae4472b --- /dev/null +++ b/src/blenderbim/test/bim/feature/style.feature @@ -0,0 +1,59 @@ +@style +Feature: Style + +Scenario: Update style colours + Given an empty IFC project + And I add a cube + And I add a material + And I press "bim.add_style" + When I press "bim.update_style_colours" + Then nothing happens + +Scenario: Remove style + Given an empty IFC project + And I add a cube + And I add a material + And I press "bim.add_style" + When I press "bim.remove_style" + Then nothing happens + +Scenario: Add style + Given an empty IFC project + And I add a cube + And I add a material + When I press "bim.add_style" + Then nothing happens + +Scenario: Unlink style + Given an empty IFC project + And I add a cube + And I add a material + And I press "bim.add_style" + When I press "bim.unlink_style" + Then nothing happens + +Scenario: Enable editing style + Given an empty IFC project + And I add a cube + And I add a material + And I press "bim.add_style" + When I press "bim.enable_editing_style" + Then nothing happens + +Scenario: Disable editing style + Given an empty IFC project + And I add a cube + And I add a material + And I press "bim.add_style" + And I press "bim.enable_editing_style" + When I press "bim.disable_editing_style" + Then nothing happens + +Scenario: Edit style + Given an empty IFC project + And I add a cube + And I add a material + And I press "bim.add_style" + And I press "bim.enable_editing_style" + When I press "bim.edit_style" + Then nothing happens diff --git a/src/blenderbim/test/bim/test_feature.py b/src/blenderbim/test/bim/test_feature.py index a7700a5df2..8f0420e855 100644 --- a/src/blenderbim/test/bim/test_feature.py +++ b/src/blenderbim/test/bim/test_feature.py @@ -49,6 +49,11 @@ def i_add_a_cube(): bpy.ops.mesh.primitive_cube_add() +@given("I add a material") +def i_add_a_material(): + bpy.context.active_object.active_material = bpy.data.materials.new("Material") + + @when(parsers.parse('I add a cube of size "{size}" at "{location}"')) def i_add_a_cube_of_size_size_at_location(size, location): bpy.ops.mesh.primitive_cube_add(size=float(size), location=[float(co) for co in location.split(",")]) diff --git a/src/blenderbim/test/core/bootstrap.py b/src/blenderbim/test/core/bootstrap.py index c8edfebabb..f4e54b2fee 100644 --- a/src/blenderbim/test/core/bootstrap.py +++ b/src/blenderbim/test/core/bootstrap.py @@ -36,43 +36,15 @@ def blender(): @pytest.fixture -def aggregator(): - prophet = Prophecy(blenderbim.core.tool.Aggregator) +def aggregate(): + prophet = Prophecy(blenderbim.core.tool.Aggregate) yield prophet prophet.verify() @pytest.fixture -def person_editor(): - prophet = Prophecy(blenderbim.core.tool.PersonEditor) - yield prophet - prophet.verify() - - -@pytest.fixture -def role_editor(): - prophet = Prophecy(blenderbim.core.tool.RoleEditor) - yield prophet - prophet.verify() - - -@pytest.fixture -def address_editor(): - prophet = Prophecy(blenderbim.core.tool.AddressEditor) - yield prophet - prophet.verify() - - -@pytest.fixture -def organisation_editor(): - prophet = Prophecy(blenderbim.core.tool.OrganisationEditor) - yield prophet - prophet.verify() - - -@pytest.fixture -def context_editor(): - prophet = Prophecy(blenderbim.core.tool.ContextEditor) +def context(): + prophet = Prophecy(blenderbim.core.tool.Context) yield prophet prophet.verify() @@ -98,6 +70,13 @@ def selector(): prophet.verify() +@pytest.fixture +def style(): + prophet = Prophecy(blenderbim.core.tool.Style) + yield prophet + prophet.verify() + + @pytest.fixture def surveyor(): prophet = Prophecy(blenderbim.core.tool.Surveyor) diff --git a/src/blenderbim/test/core/test_aggregate.py b/src/blenderbim/test/core/test_aggregate.py index 8a00453b26..cbec08656e 100644 --- a/src/blenderbim/test/core/test_aggregate.py +++ b/src/blenderbim/test/core/test_aggregate.py @@ -1,32 +1,50 @@ +# 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.aggregate as subject -from test.core.bootstrap import ifc, aggregator, collector +from test.core.bootstrap import ifc, aggregate, collector class TestEnableEditingAggregate: - def test_run(self, aggregator): - aggregator.enable_editing("obj").should_be_called() - subject.enable_editing_aggregate(aggregator, obj="obj") + def test_run(self, aggregate): + aggregate.enable_editing("obj").should_be_called() + subject.enable_editing_aggregate(aggregate, obj="obj") class TestDisableEditingAggregate: - def test_run(self, aggregator): - aggregator.disable_editing("obj").should_be_called() - subject.disable_editing_aggregate(aggregator, obj="obj") + def test_run(self, aggregate): + aggregate.disable_editing("obj").should_be_called() + subject.disable_editing_aggregate(aggregate, obj="obj") class TestAssignObject: - def test_run(self, ifc, aggregator, collector): - aggregator.can_aggregate("relating_obj", "related_obj").should_be_called().will_return(True) + def test_run(self, ifc, aggregate, collector): + aggregate.can_aggregate("relating_obj", "related_obj").should_be_called().will_return(True) ifc.get_entity("relating_obj").should_be_called().will_return("relating_object") ifc.get_entity("related_obj").should_be_called().will_return("related_object") ifc.run( "aggregate.assign_object", product="related_object", relating_object="relating_object" ).should_be_called().will_return("rel") - aggregator.disable_editing("related_obj").should_be_called() + aggregate.disable_editing("related_obj").should_be_called() collector.assign("relating_obj").should_be_called() collector.assign("related_obj").should_be_called() assert ( - subject.assign_object(ifc, aggregator, collector, relating_obj="relating_obj", related_obj="related_obj") + subject.assign_object(ifc, aggregate, collector, relating_obj="relating_obj", related_obj="related_obj") == "rel" ) diff --git a/src/blenderbim/test/core/test_context.py b/src/blenderbim/test/core/test_context.py index bdbe51bf06..55b2720291 100644 --- a/src/blenderbim/test/core/test_context.py +++ b/src/blenderbim/test/core/test_context.py @@ -17,7 +17,7 @@ # along with BlenderBIM Add-on. If not, see . import blenderbim.core.context as subject -from test.core.bootstrap import ifc, context_editor +from test.core.bootstrap import ifc, context class TestAddContext: @@ -53,22 +53,22 @@ class TestRemoveContext: class TestEnableEditingContext: - def test_run(self, context_editor): - context_editor.set_context("context").should_be_called() - context_editor.import_attributes().should_be_called() - subject.enable_editing_context(context_editor, context="context") + def test_run(self, context): + context.set_context("context").should_be_called() + context.import_attributes().should_be_called() + subject.enable_editing_context(context, context="context") class TestDisableEditingContext: - def test_run(self, context_editor): - context_editor.clear_context().should_be_called() - subject.disable_editing_context(context_editor) + def test_run(self, context): + context.clear_context().should_be_called() + subject.disable_editing_context(context) class TestEditContext: - def test_run(self, ifc, context_editor): - context_editor.get_context().should_be_called().will_return("context") - context_editor.export_attributes().should_be_called().will_return("attributes") + def test_run(self, ifc, context): + context.get_context().should_be_called().will_return("context") + context.export_attributes().should_be_called().will_return("attributes") ifc.run("context.edit_context", context="context", attributes="attributes").should_be_called() - context_editor.clear_context().should_be_called() - subject.edit_context(ifc, context_editor) + context.clear_context().should_be_called() + subject.edit_context(ifc, context) diff --git a/src/blenderbim/test/core/test_owner.py b/src/blenderbim/test/core/test_owner.py index 53c05719bf..6d7243a8a4 100644 --- a/src/blenderbim/test/core/test_owner.py +++ b/src/blenderbim/test/core/test_owner.py @@ -18,7 +18,7 @@ import blenderbim.core.owner as subject -from test.core.bootstrap import ifc, blender, person_editor, role_editor, address_editor, organisation_editor, owner +from test.core.bootstrap import ifc, owner class TestAddPerson: @@ -34,37 +34,37 @@ class TestRemovePerson: class TestEnableEditingPerson: - def test_run(self, person_editor): - person_editor.set_person("person").should_be_called() - person_editor.import_attributes().should_be_called() - subject.enable_editing_person(person_editor, person="person") + def test_run(self, owner): + owner.set_person("person").should_be_called() + owner.import_person_attributes().should_be_called() + subject.enable_editing_person(owner, person="person") class TestDisableEditingPerson: - def test_run(self, person_editor): - person_editor.clear_person().should_be_called() - subject.disable_editing_person(person_editor) + def test_run(self, owner): + owner.clear_person().should_be_called() + subject.disable_editing_person(owner) class TestEditPerson: - def test_run(self, ifc, person_editor): - person_editor.get_person().should_be_called().will_return("person") - person_editor.export_attributes().should_be_called().will_return("attributes") + def test_run(self, ifc, owner): + owner.get_person().should_be_called().will_return("person") + owner.export_person_attributes().should_be_called().will_return("attributes") ifc.run("owner.edit_person", person="person", attributes="attributes").should_be_called() - person_editor.clear_person().should_be_called() - subject.edit_person(ifc, person_editor) + owner.clear_person().should_be_called() + subject.edit_person(ifc, owner) class TestAddPersonAttribute: - def test_run(self, person_editor): - person_editor.add_attribute("name").should_be_called() - subject.add_person_attribute(person_editor, name="name") + def test_run(self, owner): + owner.add_person_attribute("name").should_be_called() + subject.add_person_attribute(owner, name="name") class TestRemovePersonAttribute: - def test_run(self, person_editor): - person_editor.remove_attribute("name", "id").should_be_called() - subject.remove_person_attribute(person_editor, name="name", id="id") + def test_run(self, owner): + owner.remove_person_attribute("name", "id").should_be_called() + subject.remove_person_attribute(owner, name="name", id="id") class TestAddRole: @@ -80,25 +80,25 @@ class TestRemoveRole: class TestEnableEditingRole: - def test_run(self, role_editor): - role_editor.set_role("role").should_be_called() - role_editor.import_attributes().should_be_called() - subject.enable_editing_role(role_editor, role="role") + def test_run(self, owner): + owner.set_role("role").should_be_called() + owner.import_role_attributes().should_be_called() + subject.enable_editing_role(owner, role="role") class TestDisableEditingRole: - def test_run(self, role_editor): - role_editor.clear_role().should_be_called() - subject.disable_editing_role(role_editor) + def test_run(self, owner): + owner.clear_role().should_be_called() + subject.disable_editing_role(owner) class TestEditRole: - def test_run(self, ifc, role_editor): - role_editor.export_attributes().should_be_called().will_return("attributes") - role_editor.get_role().should_be_called().will_return("role") + def test_run(self, ifc, owner): + owner.export_role_attributes().should_be_called().will_return("attributes") + owner.get_role().should_be_called().will_return("role") ifc.run("owner.edit_role", role="role", attributes="attributes").should_be_called() - role_editor.clear_role().should_be_called() - subject.edit_role(ifc, role_editor) + owner.clear_role().should_be_called() + subject.edit_role(ifc, owner) class TestAddAddress: @@ -116,37 +116,37 @@ class TestRemoveAddress: class TestEnableEditingAddress: - def test_run(self, address_editor): - address_editor.set_address("address").should_be_called() - address_editor.import_attributes().should_be_called() - subject.enable_editing_address(address_editor, address="address") + def test_run(self, owner): + owner.set_address("address").should_be_called() + owner.import_address_attributes().should_be_called() + subject.enable_editing_address(owner, address="address") class TestDisableEditingAddress: - def test_run(self, address_editor): - address_editor.clear_address().should_be_called() - subject.disable_editing_address(address_editor) + def test_run(self, owner): + owner.clear_address().should_be_called() + subject.disable_editing_address(owner) class TestEditAddress: - def test_run(self, ifc, address_editor): - address_editor.get_address().should_be_called().will_return("address") - address_editor.export_attributes().should_be_called().will_return("attributes") + def test_run(self, ifc, owner): + owner.get_address().should_be_called().will_return("address") + owner.export_address_attributes().should_be_called().will_return("attributes") ifc.run("owner.edit_address", address="address", attributes="attributes").should_be_called() - address_editor.clear_address().should_be_called() - subject.edit_address(ifc, address_editor) + owner.clear_address().should_be_called() + subject.edit_address(ifc, owner) class TestAddAddressAttribute: - def test_run(self, address_editor): - address_editor.add_attribute("name").should_be_called() - subject.add_address_attribute(address_editor, name="name") + def test_run(self, owner): + owner.add_address_attribute("name").should_be_called() + subject.add_address_attribute(owner, name="name") class TestRemoveAddressAttribute: - def test_run(self, address_editor): - address_editor.remove_attribute("name", "id").should_be_called() - subject.remove_address_attribute(address_editor, name="name", id="id") + def test_run(self, owner): + owner.remove_address_attribute("name", "id").should_be_called() + subject.remove_address_attribute(owner, name="name", id="id") class TestAddOrganisation: @@ -162,25 +162,25 @@ class TestRemoveOrganisation: class TestEnableEditingOrganisation: - def test_run(self, organisation_editor): - organisation_editor.set_organisation("organisation").should_be_called() - organisation_editor.import_attributes().should_be_called() - subject.enable_editing_organisation(organisation_editor, organisation="organisation") + def test_run(self, owner): + owner.set_organisation("organisation").should_be_called() + owner.import_organisation_attributes().should_be_called() + subject.enable_editing_organisation(owner, organisation="organisation") class TestDisableEditingOrganisation: - def test_run(self, organisation_editor): - organisation_editor.clear_organisation().should_be_called() - subject.disable_editing_organisation(organisation_editor) + def test_run(self, owner): + owner.clear_organisation().should_be_called() + subject.disable_editing_organisation(owner) class TestEditOrganisation: - def test_run(self, ifc, organisation_editor): - organisation_editor.get_organisation().should_be_called().will_return("organisation") - organisation_editor.export_attributes().should_be_called().will_return("attributes") + def test_run(self, ifc, owner): + owner.get_organisation().should_be_called().will_return("organisation") + owner.export_organisation_attributes().should_be_called().will_return("attributes") ifc.run("owner.edit_organisation", organisation="organisation", attributes="attributes").should_be_called() - organisation_editor.clear_organisation().should_be_called() - subject.edit_organisation(ifc, organisation_editor) + owner.clear_organisation().should_be_called() + subject.edit_organisation(ifc, owner) class TestAddPersonAndOrganisation: diff --git a/src/blenderbim/test/core/test_style.py b/src/blenderbim/test/core/test_style.py new file mode 100644 index 0000000000..78a46b6019 --- /dev/null +++ b/src/blenderbim/test/core/test_style.py @@ -0,0 +1,98 @@ +# 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.style as subject +from test.core.bootstrap import ifc, style + + +class TestAddStyle: + def test_it_adds_a_style_with_rendering_attributes(self, ifc, style): + style.get_name("obj").should_be_called().will_return("name") + ifc.run("style.add_style", name="name").should_be_called().will_return("style") + style.link("style", "obj").should_be_called() + style.get_surface_rendering_attributes("obj").should_be_called().will_return("attributes") + ifc.run( + "style.add_surface_style", style="style", ifc_class="IfcSurfaceStyleRendering", attributes="attributes" + ).should_be_called() + ifc.get_entity("obj").should_be_called().will_return(None) + assert subject.add_style(ifc, style, obj="obj") == "style" + + def test_adding_a_style_linked_to_a_material(self, ifc, style): + style.get_name("obj").should_be_called().will_return("name") + ifc.run("style.add_style", name="name").should_be_called().will_return("style") + style.link("style", "obj").should_be_called() + style.get_surface_rendering_attributes("obj").should_be_called().will_return("attributes") + ifc.run( + "style.add_surface_style", style="style", ifc_class="IfcSurfaceStyleRendering", attributes="attributes" + ).should_be_called() + ifc.get_entity("obj").should_be_called().will_return("material") + style.get_context("obj").should_be_called().will_return("context") + ifc.run("style.assign_material_style", material="material", style="style", context="context").should_be_called() + assert subject.add_style(ifc, style, obj="obj") == "style" + + +class TestRemoveStyle: + def test_run(self, ifc, style): + style.get_style("obj").should_be_called().will_return("style") + ifc.run("style.remove_style", style="style").should_be_called() + style.unlink(obj="obj").should_be_called() + subject.remove_style(ifc, style, obj="obj") + + +class TestUpdateStyleColours: + def test_updating_rendering_colours_if_available(self, ifc, style): + style.get_surface_rendering_style("obj").should_be_called().will_return("style") + style.get_surface_rendering_attributes("obj").should_be_called().will_return("attributes") + ifc.run("style.edit_surface_style", style="style", attributes="attributes").should_be_called() + subject.update_style_colours(ifc, style, obj="obj") + + def test_updating_shading_colours_as_a_fallback_if_available(self, ifc, style): + style.get_surface_rendering_style("obj").should_be_called().will_return(None) + style.get_surface_shading_style("obj").should_be_called().will_return("style") + style.get_surface_shading_attributes("obj").should_be_called().will_return("attributes") + ifc.run("style.edit_surface_style", style="style", attributes="attributes").should_be_called() + subject.update_style_colours(ifc, style, obj="obj") + + +class TestUnlinkStyle: + def test_run(self, style): + style.unlink("obj").should_be_called() + subject.unlink_style(style, obj="obj") + + +class TestEnableEditingStyle: + def test_run(self, style): + style.enable_editing("obj").should_be_called() + style.get_style("obj").should_be_called().will_return("style") + style.import_surface_attributes("style", "obj").should_be_called() + subject.enable_editing_style(style, obj="obj") + + +class TestDisableEditingStyle: + def test_run(self, style): + style.disable_editing("obj").should_be_called() + subject.disable_editing_style(style, obj="obj") + + +class TestEditStyle: + def test_run(self, ifc, style): + style.get_style("obj").should_be_called().will_return("style") + style.export_surface_attributes("obj").should_be_called().will_return("attributes") + ifc.run("style.edit_presentation_style", style="style", attributes="attributes").should_be_called() + style.disable_editing("obj").should_be_called() + subject.edit_style(ifc, style, obj="obj") diff --git a/src/blenderbim/test/tool/test_address_editor.py b/src/blenderbim/test/tool/test_address_editor.py deleted file mode 100644 index c9350f13f3..0000000000 --- a/src/blenderbim/test/tool/test_address_editor.py +++ /dev/null @@ -1,197 +0,0 @@ -# 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 test.bim.bootstrap -import blenderbim.core.tool -import blenderbim.tool as tool -from blenderbim.tool.address_editor import AddressEditor as subject - - -class TestImplementsTool(test.bim.bootstrap.NewFile): - def test_run(self): - assert isinstance(subject(), blenderbim.core.tool.AddressEditor) - - -class TestSetAddress(test.bim.bootstrap.NewFile): - def test_run(self): - ifc = ifcopenshell.file() - address = ifc.createIfcPostalAddress() - subject().set_address(address) - assert bpy.context.scene.BIMOwnerProperties.active_address_id == address.id() - - -class TestImportAttributes(test.bim.bootstrap.NewFile): - def test_importing_a_postal_address(self): - ifc = ifcopenshell.file() - tool.Ifc().set(ifc) - address = ifc.createIfcPostalAddress() - address.Purpose = "USERDEFINED" - address.Description = "Description" - address.UserDefinedPurpose = "UserDefinedPurpose" - address.InternalLocation = "InternalLocation" - address.AddressLines = ["Address", "Lines"] - address.PostalBox = "PostalBox" - address.Town = "Town" - address.Region = "Region" - address.PostalCode = "PostalCode" - address.Country = "Country" - subject().set_address(address) - subject().import_attributes() - props = bpy.context.scene.BIMOwnerProperties - assert props.address_attributes.get("Purpose").enum_value == "USERDEFINED" - assert props.address_attributes.get("Description").string_value == "Description" - assert props.address_attributes.get("UserDefinedPurpose").string_value == "UserDefinedPurpose" - assert props.address_attributes.get("InternalLocation").string_value == "InternalLocation" - assert props.address_attributes.get("PostalBox").string_value == "PostalBox" - assert props.address_attributes.get("Town").string_value == "Town" - assert props.address_attributes.get("Region").string_value == "Region" - assert props.address_attributes.get("PostalCode").string_value == "PostalCode" - assert props.address_attributes.get("Country").string_value == "Country" - assert len(props.address_lines) == 2 - assert props.address_lines[0].name == "Address" - assert props.address_lines[1].name == "Lines" - - def test_importing_a_postal_address_twice(self): - self.test_importing_a_postal_address() - ifc = tool.Ifc().get() - address = ifc.createIfcPostalAddress() - address.Purpose = "OFFICE" - subject().set_address(address) - subject().import_attributes() - props = bpy.context.scene.BIMOwnerProperties - assert props.address_attributes.get("Purpose").enum_value == "OFFICE" - assert len(props.address_lines) == 0 - - def test_importing_a_telecom_address(self): - ifc = ifcopenshell.file() - tool.Ifc().set(ifc) - address = ifc.createIfcTelecomAddress() - address.Purpose = "USERDEFINED" - address.Description = "Description" - address.UserDefinedPurpose = "UserDefinedPurpose" - address.TelephoneNumbers = ["Telephone", "Numbers"] - address.FacsimileNumbers = ["Facsimile", "Numbers"] - address.PagerNumber = "PagerNumber" - address.ElectronicMailAddresses = ["Electronic", "Mail", "Addresses"] - address.WWWHomePageURL = "WWWHomePageURL" - address.MessagingIDs = ["Messaging", "IDs"] - subject().set_address(address) - subject().import_attributes() - props = bpy.context.scene.BIMOwnerProperties - assert props.address_attributes.get("Purpose").enum_value == "USERDEFINED" - assert props.address_attributes.get("Description").string_value == "Description" - assert props.address_attributes.get("UserDefinedPurpose").string_value == "UserDefinedPurpose" - assert [a.name for a in props.telephone_numbers] == ["Telephone", "Numbers"] - assert [a.name for a in props.facsimile_numbers] == ["Facsimile", "Numbers"] - assert [a.name for a in props.electronic_mail_addresses] == ["Electronic", "Mail", "Addresses"] - assert [a.name for a in props.messaging_ids] == ["Messaging", "IDs"] - - def test_importing_a_telecom_address_twice(self): - self.test_importing_a_telecom_address() - ifc = tool.Ifc().get() - address = ifc.createIfcTelecomAddress() - address.Purpose = "OFFICE" - subject().set_address(address) - subject().import_attributes() - props = bpy.context.scene.BIMOwnerProperties - assert props.address_attributes.get("Purpose").enum_value == "OFFICE" - assert len(props.telephone_numbers) == 0 - assert len(props.facsimile_numbers) == 0 - assert len(props.electronic_mail_addresses) == 0 - assert len(props.messaging_ids) == 0 - - -class TestClearAddress(test.bim.bootstrap.NewFile): - def test_run(self): - ifc = ifcopenshell.file() - address = ifc.createIfcPostalAddress() - subject().set_address(address) - subject().clear_address() - assert bpy.context.scene.BIMOwnerProperties.active_address_id == 0 - - -class TestGetAddress(test.bim.bootstrap.NewFile): - def test_run(self): - ifc = ifcopenshell.file() - tool.Ifc().set(ifc) - address = ifc.createIfcPostalAddress() - subject().set_address(address) - assert subject().get_address() == address - - -class TestExportAttributes(test.bim.bootstrap.NewFile): - def test_exporting_a_postal_address(self): - TestImportAttributes().test_importing_a_postal_address() - assert subject().export_attributes() == { - "Purpose": "USERDEFINED", - "Description": "Description", - "UserDefinedPurpose": "UserDefinedPurpose", - "InternalLocation": "InternalLocation", - "AddressLines": ["Address", "Lines"], - "PostalBox": "PostalBox", - "Town": "Town", - "Region": "Region", - "PostalCode": "PostalCode", - "Country": "Country", - } - - def test_exporting_a_telecom_address(self): - TestImportAttributes().test_importing_a_telecom_address() - assert subject().export_attributes() == { - "Purpose": "USERDEFINED", - "Description": "Description", - "UserDefinedPurpose": "UserDefinedPurpose", - "TelephoneNumbers": ["Telephone", "Numbers"], - "FacsimileNumbers": ["Facsimile", "Numbers"], - "PagerNumber": "PagerNumber", - "ElectronicMailAddresses": ["Electronic", "Mail", "Addresses"], - "WWWHomePageURL": "WWWHomePageURL", - "MessagingIDs": ["Messaging", "IDs"], - } - - -class TestAddAttribute(test.bim.bootstrap.NewFile): - def test_run(self): - subject().add_attribute("AddressLines") - subject().add_attribute("TelephoneNumbers") - subject().add_attribute("FacsimileNumbers") - subject().add_attribute("ElectronicMailAddresses") - subject().add_attribute("MessagingIDs") - props = bpy.context.scene.BIMOwnerProperties - assert len(props.address_lines) == 1 - assert len(props.telephone_numbers) == 1 - assert len(props.facsimile_numbers) == 1 - assert len(props.electronic_mail_addresses) == 1 - assert len(props.messaging_ids) == 1 - - -class TestRemoveAddress(test.bim.bootstrap.NewFile): - TestAddAttribute().test_run() - subject().remove_attribute("AddressLines", 0) - subject().remove_attribute("TelephoneNumbers", 0) - subject().remove_attribute("FacsimileNumbers", 0) - subject().remove_attribute("ElectronicMailAddresses", 0) - subject().remove_attribute("MessagingIDs", 0) - props = bpy.context.scene.BIMOwnerProperties - assert len(props.address_lines) == 0 - assert len(props.telephone_numbers) == 0 - assert len(props.facsimile_numbers) == 0 - assert len(props.electronic_mail_addresses) == 0 - assert len(props.messaging_ids) == 0 diff --git a/src/blenderbim/test/tool/test_aggregator.py b/src/blenderbim/test/tool/test_aggregate.py similarity index 82% rename from src/blenderbim/test/tool/test_aggregator.py rename to src/blenderbim/test/tool/test_aggregate.py index 92ee92ebe0..5af016a34f 100644 --- a/src/blenderbim/test/tool/test_aggregator.py +++ b/src/blenderbim/test/tool/test_aggregate.py @@ -1,14 +1,32 @@ +# 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 test.bim.bootstrap import NewFile -from blenderbim.tool.aggregator import Aggregator as subject +from blenderbim.tool.aggregate import Aggregate as subject class TestImplementsTool(NewFile): def test_run(self): - assert isinstance(subject(), blenderbim.core.tool.Aggregator) + assert isinstance(subject(), blenderbim.core.tool.Aggregate) class TestEnableEditing(NewFile): diff --git a/src/blenderbim/test/tool/test_context_editor.py b/src/blenderbim/test/tool/test_context.py similarity index 98% rename from src/blenderbim/test/tool/test_context_editor.py rename to src/blenderbim/test/tool/test_context.py index 9a13e56c46..8b8a4a3676 100644 --- a/src/blenderbim/test/tool/test_context_editor.py +++ b/src/blenderbim/test/tool/test_context.py @@ -21,12 +21,12 @@ import ifcopenshell import test.bim.bootstrap import blenderbim.core.tool import blenderbim.tool as tool -from blenderbim.tool.context_editor import ContextEditor as subject +from blenderbim.tool.context import Context as subject class TestImplementsTool(test.bim.bootstrap.NewFile): def test_run(self): - assert isinstance(subject(), blenderbim.core.tool.ContextEditor) + assert isinstance(subject(), blenderbim.core.tool.Context) class TestSetContext(test.bim.bootstrap.NewFile): diff --git a/src/blenderbim/test/tool/test_organisation_editor.py b/src/blenderbim/test/tool/test_organisation_editor.py deleted file mode 100644 index d6bcfb4334..0000000000 --- a/src/blenderbim/test/tool/test_organisation_editor.py +++ /dev/null @@ -1,94 +0,0 @@ -# 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 test.bim.bootstrap -import blenderbim.core.tool -import blenderbim.tool as tool -from blenderbim.tool.organisation_editor import OrganisationEditor as subject - - -class TestImplementsTool(test.bim.bootstrap.NewFile): - def test_run(self): - assert isinstance(subject(), blenderbim.core.tool.OrganisationEditor) - - -class TestSetOrganisation(test.bim.bootstrap.NewFile): - def test_run(self): - organisation = ifcopenshell.file().createIfcOrganization() - subject().set_organisation(organisation) - assert bpy.context.scene.BIMOwnerProperties.active_organisation_id == organisation.id() - - -class TestImportAttributes(test.bim.bootstrap.NewFile): - def test_run(self): - ifc = ifcopenshell.file() - tool.Ifc().set(ifc) - organisation = ifc.createIfcOrganization() - organisation.Identification = "Identification" - organisation.Name = "Name" - organisation.Description = "Description" - subject().set_organisation(organisation) - subject().import_attributes() - props = bpy.context.scene.BIMOwnerProperties - assert props.organisation_attributes.get("Identification").string_value == "Identification" - assert props.organisation_attributes.get("Name").string_value == "Name" - assert props.organisation_attributes.get("Description").string_value == "Description" - - def test_overwriting_a_previous_import(self): - ifc = ifcopenshell.file() - tool.Ifc().set(ifc) - organisation = ifc.createIfcOrganization() - organisation.Identification = "Identification" - organisation.Description = "Description" - subject().set_organisation(organisation) - subject().import_attributes() - organisation.Identification = "Identification2" - organisation.Description = None - subject().import_attributes() - props = bpy.context.scene.BIMOwnerProperties - assert props.organisation_attributes.get("Identification").string_value == "Identification2" - assert props.organisation_attributes.get("Description").string_value == "" - - -class TestClearOrganisation(test.bim.bootstrap.NewFile): - def test_run(self): - props = bpy.context.scene.BIMOwnerProperties - props.active_organisation_id = 1 - subject().clear_organisation() - assert props.active_organisation_id == 0 - - -class TestExportAttributes(test.bim.bootstrap.NewFile): - def test_run(self): - TestImportAttributes().test_run() - assert subject().export_attributes() == { - "Identification": "Identification", - "Name": "Name", - "Description": "Description", - } - - -class TestGetOrganisation(test.bim.bootstrap.NewFile): - def test_run(self): - ifc = ifcopenshell.file() - tool.Ifc().set(ifc) - organisation = ifc.createIfcOrganization() - subject().set_organisation(organisation) - assert subject().get_organisation() == organisation diff --git a/src/blenderbim/test/tool/test_owner.py b/src/blenderbim/test/tool/test_owner.py index 8231d6b296..70c44707e2 100644 --- a/src/blenderbim/test/tool/test_owner.py +++ b/src/blenderbim/test/tool/test_owner.py @@ -51,3 +51,428 @@ class TestClearUser(test.bim.bootstrap.NewFile): TestSetUser().test_run() subject.clear_user() assert bpy.context.scene.BIMOwnerProperties.active_user_id == 0 + + +class TestSetAddress(test.bim.bootstrap.NewFile): + def test_run(self): + ifc = ifcopenshell.file() + address = ifc.createIfcPostalAddress() + subject().set_address(address) + assert bpy.context.scene.BIMOwnerProperties.active_address_id == address.id() + + +class TestImportAddressAttributes(test.bim.bootstrap.NewFile): + def test_importing_a_postal_address(self): + ifc = ifcopenshell.file() + tool.Ifc().set(ifc) + address = ifc.createIfcPostalAddress() + address.Purpose = "USERDEFINED" + address.Description = "Description" + address.UserDefinedPurpose = "UserDefinedPurpose" + address.InternalLocation = "InternalLocation" + address.AddressLines = ["Address", "Lines"] + address.PostalBox = "PostalBox" + address.Town = "Town" + address.Region = "Region" + address.PostalCode = "PostalCode" + address.Country = "Country" + subject().set_address(address) + subject().import_address_attributes() + props = bpy.context.scene.BIMOwnerProperties + assert props.address_attributes.get("Purpose").enum_value == "USERDEFINED" + assert props.address_attributes.get("Description").string_value == "Description" + assert props.address_attributes.get("UserDefinedPurpose").string_value == "UserDefinedPurpose" + assert props.address_attributes.get("InternalLocation").string_value == "InternalLocation" + assert props.address_attributes.get("PostalBox").string_value == "PostalBox" + assert props.address_attributes.get("Town").string_value == "Town" + assert props.address_attributes.get("Region").string_value == "Region" + assert props.address_attributes.get("PostalCode").string_value == "PostalCode" + assert props.address_attributes.get("Country").string_value == "Country" + assert len(props.address_lines) == 2 + assert props.address_lines[0].name == "Address" + assert props.address_lines[1].name == "Lines" + + def test_importing_a_postal_address_twice(self): + self.test_importing_a_postal_address() + ifc = tool.Ifc().get() + address = ifc.createIfcPostalAddress() + address.Purpose = "OFFICE" + subject().set_address(address) + subject().import_address_attributes() + props = bpy.context.scene.BIMOwnerProperties + assert props.address_attributes.get("Purpose").enum_value == "OFFICE" + assert len(props.address_lines) == 0 + + def test_importing_a_telecom_address(self): + ifc = ifcopenshell.file() + tool.Ifc().set(ifc) + address = ifc.createIfcTelecomAddress() + address.Purpose = "USERDEFINED" + address.Description = "Description" + address.UserDefinedPurpose = "UserDefinedPurpose" + address.TelephoneNumbers = ["Telephone", "Numbers"] + address.FacsimileNumbers = ["Facsimile", "Numbers"] + address.PagerNumber = "PagerNumber" + address.ElectronicMailAddresses = ["Electronic", "Mail", "Addresses"] + address.WWWHomePageURL = "WWWHomePageURL" + address.MessagingIDs = ["Messaging", "IDs"] + subject().set_address(address) + subject().import_address_attributes() + props = bpy.context.scene.BIMOwnerProperties + assert props.address_attributes.get("Purpose").enum_value == "USERDEFINED" + assert props.address_attributes.get("Description").string_value == "Description" + assert props.address_attributes.get("UserDefinedPurpose").string_value == "UserDefinedPurpose" + assert [a.name for a in props.telephone_numbers] == ["Telephone", "Numbers"] + assert [a.name for a in props.facsimile_numbers] == ["Facsimile", "Numbers"] + assert [a.name for a in props.electronic_mail_addresses] == ["Electronic", "Mail", "Addresses"] + assert [a.name for a in props.messaging_ids] == ["Messaging", "IDs"] + + def test_importing_a_telecom_address_twice(self): + self.test_importing_a_telecom_address() + ifc = tool.Ifc().get() + address = ifc.createIfcTelecomAddress() + address.Purpose = "OFFICE" + subject().set_address(address) + subject().import_address_attributes() + props = bpy.context.scene.BIMOwnerProperties + assert props.address_attributes.get("Purpose").enum_value == "OFFICE" + assert len(props.telephone_numbers) == 0 + assert len(props.facsimile_numbers) == 0 + assert len(props.electronic_mail_addresses) == 0 + assert len(props.messaging_ids) == 0 + + +class TestClearAddress(test.bim.bootstrap.NewFile): + def test_run(self): + ifc = ifcopenshell.file() + address = ifc.createIfcPostalAddress() + subject().set_address(address) + subject().clear_address() + assert bpy.context.scene.BIMOwnerProperties.active_address_id == 0 + + +class TestGetAddress(test.bim.bootstrap.NewFile): + def test_run(self): + ifc = ifcopenshell.file() + tool.Ifc().set(ifc) + address = ifc.createIfcPostalAddress() + subject().set_address(address) + assert subject().get_address() == address + + +class TestExportAttributes(test.bim.bootstrap.NewFile): + def test_exporting_a_postal_address(self): + TestImportAddressAttributes().test_importing_a_postal_address() + assert subject().export_address_attributes() == { + "Purpose": "USERDEFINED", + "Description": "Description", + "UserDefinedPurpose": "UserDefinedPurpose", + "InternalLocation": "InternalLocation", + "AddressLines": ["Address", "Lines"], + "PostalBox": "PostalBox", + "Town": "Town", + "Region": "Region", + "PostalCode": "PostalCode", + "Country": "Country", + } + + def test_exporting_a_telecom_address(self): + TestImportAddressAttributes().test_importing_a_telecom_address() + assert subject().export_address_attributes() == { + "Purpose": "USERDEFINED", + "Description": "Description", + "UserDefinedPurpose": "UserDefinedPurpose", + "TelephoneNumbers": ["Telephone", "Numbers"], + "FacsimileNumbers": ["Facsimile", "Numbers"], + "PagerNumber": "PagerNumber", + "ElectronicMailAddresses": ["Electronic", "Mail", "Addresses"], + "WWWHomePageURL": "WWWHomePageURL", + "MessagingIDs": ["Messaging", "IDs"], + } + + +class TestAddAddressAttribute(test.bim.bootstrap.NewFile): + def test_run(self): + subject().add_address_attribute("AddressLines") + subject().add_address_attribute("TelephoneNumbers") + subject().add_address_attribute("FacsimileNumbers") + subject().add_address_attribute("ElectronicMailAddresses") + subject().add_address_attribute("MessagingIDs") + props = bpy.context.scene.BIMOwnerProperties + assert len(props.address_lines) == 1 + assert len(props.telephone_numbers) == 1 + assert len(props.facsimile_numbers) == 1 + assert len(props.electronic_mail_addresses) == 1 + assert len(props.messaging_ids) == 1 + + +class TestRemoveAddressAttribute(test.bim.bootstrap.NewFile): + TestAddAddressAttribute().test_run() + subject().remove_address_attribute("AddressLines", 0) + subject().remove_address_attribute("TelephoneNumbers", 0) + subject().remove_address_attribute("FacsimileNumbers", 0) + subject().remove_address_attribute("ElectronicMailAddresses", 0) + subject().remove_address_attribute("MessagingIDs", 0) + props = bpy.context.scene.BIMOwnerProperties + assert len(props.address_lines) == 0 + assert len(props.telephone_numbers) == 0 + assert len(props.facsimile_numbers) == 0 + assert len(props.electronic_mail_addresses) == 0 + assert len(props.messaging_ids) == 0 + + +class TestSetOrganisation(test.bim.bootstrap.NewFile): + def test_run(self): + organisation = ifcopenshell.file().createIfcOrganization() + subject().set_organisation(organisation) + assert bpy.context.scene.BIMOwnerProperties.active_organisation_id == organisation.id() + + +class TestImportOrganisationAttributes(test.bim.bootstrap.NewFile): + def test_run(self): + ifc = ifcopenshell.file() + tool.Ifc().set(ifc) + organisation = ifc.createIfcOrganization() + organisation.Identification = "Identification" + organisation.Name = "Name" + organisation.Description = "Description" + subject().set_organisation(organisation) + subject().import_organisation_attributes() + props = bpy.context.scene.BIMOwnerProperties + assert props.organisation_attributes.get("Identification").string_value == "Identification" + assert props.organisation_attributes.get("Name").string_value == "Name" + assert props.organisation_attributes.get("Description").string_value == "Description" + + def test_overwriting_a_previous_import(self): + ifc = ifcopenshell.file() + tool.Ifc().set(ifc) + organisation = ifc.createIfcOrganization() + organisation.Identification = "Identification" + organisation.Description = "Description" + subject().set_organisation(organisation) + subject().import_organisation_attributes() + organisation.Identification = "Identification2" + organisation.Description = None + subject().import_organisation_attributes() + props = bpy.context.scene.BIMOwnerProperties + assert props.organisation_attributes.get("Identification").string_value == "Identification2" + assert props.organisation_attributes.get("Description").string_value == "" + + +class TestClearOrganisation(test.bim.bootstrap.NewFile): + def test_run(self): + props = bpy.context.scene.BIMOwnerProperties + props.active_organisation_id = 1 + subject().clear_organisation() + assert props.active_organisation_id == 0 + + +class TestExportOrganisationAttributes(test.bim.bootstrap.NewFile): + def test_run(self): + TestImportOrganisationAttributes().test_run() + assert subject().export_organisation_attributes() == { + "Identification": "Identification", + "Name": "Name", + "Description": "Description", + } + + +class TestGetOrganisation(test.bim.bootstrap.NewFile): + def test_run(self): + ifc = ifcopenshell.file() + tool.Ifc().set(ifc) + organisation = ifc.createIfcOrganization() + subject().set_organisation(organisation) + assert subject().get_organisation() == organisation + + +class TestSetPerson(test.bim.bootstrap.NewFile): + def test_run(self): + person = ifcopenshell.file().createIfcPerson() + subject().set_person(person) + assert bpy.context.scene.BIMOwnerProperties.active_person_id == person.id() + + +class TestImportPersonAttributes(test.bim.bootstrap.NewFile): + def test_run(self): + ifc = ifcopenshell.file() + tool.Ifc().set(ifc) + person = ifc.createIfcPerson() + person.Identification = "identification" + person.GivenName = "given_name" + person.FamilyName = "family_name" + person.MiddleNames = ("middle", "names") + person.PrefixTitles = ("prefix", "titles") + person.SuffixTitles = ("suffix", "titles") + subject().set_person(person) + subject().import_person_attributes() + props = bpy.context.scene.BIMOwnerProperties + assert props.person_attributes.get("Identification").string_value == "identification" + assert props.person_attributes.get("GivenName").string_value == "given_name" + assert props.person_attributes.get("FamilyName").string_value == "family_name" + assert len(props.middle_names) == 2 + assert props.middle_names[0].name == "middle" + assert props.middle_names[1].name == "names" + assert len(props.prefix_titles) == 2 + assert props.prefix_titles[0].name == "prefix" + assert props.prefix_titles[1].name == "titles" + assert len(props.suffix_titles) == 2 + assert props.suffix_titles[0].name == "suffix" + assert props.suffix_titles[1].name == "titles" + + def test_overwriting_a_previous_import(self): + ifc = ifcopenshell.file() + tool.Ifc().set(ifc) + person = ifc.createIfcPerson() + person.Identification = "identification" + person.GivenName = "given_name" + subject().set_person(person) + subject().import_person_attributes() + person.Identification = "identification2" + person.GivenName = None + subject().import_person_attributes() + props = bpy.context.scene.BIMOwnerProperties + assert props.person_attributes.get("Identification").string_value == "identification2" + assert props.person_attributes.get("GivenName").string_value == "" + + +class TestClearPerson(test.bim.bootstrap.NewFile): + def test_run(self): + props = bpy.context.scene.BIMOwnerProperties + props.active_person_id = 1 + subject().clear_person() + assert props.active_person_id == 0 + + +class TestExportPersonAttributes(test.bim.bootstrap.NewFile): + def test_run(self): + ifc = ifcopenshell.file() + tool.Ifc().set(ifc) + person = ifc.createIfcPerson() + person.Identification = "identification" + person.GivenName = "given_name" + person.FamilyName = "family_name" + person.MiddleNames = ("middle", "names") + person.PrefixTitles = ("prefix", "titles") + person.SuffixTitles = ("suffix", "titles") + subject().set_person(person) + subject().import_person_attributes() + assert subject().export_person_attributes() == { + "Identification": "identification", + "GivenName": "given_name", + "FamilyName": "family_name", + "MiddleNames": ["middle", "names"], + "PrefixTitles": ["prefix", "titles"], + "SuffixTitles": ["suffix", "titles"], + } + + def test_getting_empty_list_attributes_as_none(self): + ifc = ifcopenshell.file() + tool.Ifc().set(ifc) + result = subject().export_person_attributes() + assert result["MiddleNames"] is None + assert result["PrefixTitles"] is None + assert result["SuffixTitles"] is None + + +class TestGetPerson(test.bim.bootstrap.NewFile): + def test_run(self): + ifc = ifcopenshell.file() + tool.Ifc().set(ifc) + person = ifc.createIfcPerson() + subject().set_person(person) + assert subject().get_person() == person + + +class TestAddPersonAttribute(test.bim.bootstrap.NewFile): + def test_run(self): + subject().add_person_attribute("MiddleNames") + subject().add_person_attribute("PrefixTitles") + subject().add_person_attribute("SuffixTitles") + props = bpy.context.scene.BIMOwnerProperties + assert len(props.middle_names) == 1 + assert len(props.prefix_titles) == 1 + assert len(props.suffix_titles) == 1 + + +class TestRemovePersonAttribute(test.bim.bootstrap.NewFile): + def test_run(self): + subject().add_person_attribute("MiddleNames") + subject().remove_person_attribute("MiddleNames", 0) + subject().add_person_attribute("PrefixTitles") + subject().remove_person_attribute("PrefixTitles", 0) + subject().add_person_attribute("SuffixTitles") + subject().remove_person_attribute("SuffixTitles", 0) + props = bpy.context.scene.BIMOwnerProperties + assert len(props.middle_names) == 0 + assert len(props.prefix_titles) == 0 + assert len(props.suffix_titles) == 0 + + +class TestSetRole(test.bim.bootstrap.NewFile): + def test_run(self): + role = ifcopenshell.file().createIfcActorRole() + subject().set_role(role) + assert bpy.context.scene.BIMOwnerProperties.active_role_id == role.id() + + +class TestImportRoleAttributes(test.bim.bootstrap.NewFile): + def test_run(self): + ifc = ifcopenshell.file() + tool.Ifc().set(ifc) + role = ifc.createIfcActorRole() + role.Role = "USERDEFINED" + role.UserDefinedRole = "UserDefinedRole" + role.Description = "Description" + subject().set_role(role) + subject().import_role_attributes() + props = bpy.context.scene.BIMOwnerProperties + assert props.role_attributes.get("Role").enum_value == "USERDEFINED" + assert props.role_attributes.get("UserDefinedRole").string_value == "UserDefinedRole" + assert props.role_attributes.get("Description").string_value == "Description" + + def test_importing_twice(self): + ifc = ifcopenshell.file() + tool.Ifc().set(ifc) + role = ifc.createIfcActorRole() + role.Role = "USERDEFINED" + subject().set_role(role) + subject().import_role_attributes() + role.Role = "ARCHITECT" + subject().import_role_attributes() + props = bpy.context.scene.BIMOwnerProperties + assert props.role_attributes.get("Role").enum_value == "ARCHITECT" + + +class TestClearRole(test.bim.bootstrap.NewFile): + def test_run(self): + role = ifcopenshell.file().createIfcActorRole() + subject().set_role(role) + subject().clear_role() + assert bpy.context.scene.BIMOwnerProperties.active_role_id == 0 + + +class TestGetRole(test.bim.bootstrap.NewFile): + def test_run(self): + ifc = ifcopenshell.file() + tool.Ifc().set(ifc) + role = ifc.createIfcActorRole() + subject().set_role(role) + assert subject().get_role() == role + + +class TestExportRoleAttributes(test.bim.bootstrap.NewFile): + def test_run(self): + ifc = ifcopenshell.file() + tool.Ifc().set(ifc) + role = ifc.createIfcActorRole() + role.Role = "USERDEFINED" + role.UserDefinedRole = "UserDefinedRole" + role.Description = "Description" + subject().set_role(role) + subject().import_role_attributes() + assert subject().export_role_attributes() == { + "Role": "USERDEFINED", + "UserDefinedRole": "UserDefinedRole", + "Description": "Description", + } diff --git a/src/blenderbim/test/tool/test_person_editor.py b/src/blenderbim/test/tool/test_person_editor.py deleted file mode 100644 index 949a6f544c..0000000000 --- a/src/blenderbim/test/tool/test_person_editor.py +++ /dev/null @@ -1,152 +0,0 @@ -# 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 test.bim.bootstrap -import blenderbim.core.tool -import blenderbim.tool as tool -from blenderbim.tool.person_editor import PersonEditor as subject - - -class TestImplementsTool(test.bim.bootstrap.NewFile): - def test_run(self): - assert isinstance(subject(), blenderbim.core.tool.PersonEditor) - - -class TestSetPerson(test.bim.bootstrap.NewFile): - def test_run(self): - person = ifcopenshell.file().createIfcPerson() - subject().set_person(person) - assert bpy.context.scene.BIMOwnerProperties.active_person_id == person.id() - - -class TestImportAttributes(test.bim.bootstrap.NewFile): - def test_run(self): - ifc = ifcopenshell.file() - tool.Ifc().set(ifc) - person = ifc.createIfcPerson() - person.Identification = "identification" - person.GivenName = "given_name" - person.FamilyName = "family_name" - person.MiddleNames = ("middle", "names") - person.PrefixTitles = ("prefix", "titles") - person.SuffixTitles = ("suffix", "titles") - subject().set_person(person) - subject().import_attributes() - props = bpy.context.scene.BIMOwnerProperties - assert props.person_attributes.get("Identification").string_value == "identification" - assert props.person_attributes.get("GivenName").string_value == "given_name" - assert props.person_attributes.get("FamilyName").string_value == "family_name" - assert len(props.middle_names) == 2 - assert props.middle_names[0].name == "middle" - assert props.middle_names[1].name == "names" - assert len(props.prefix_titles) == 2 - assert props.prefix_titles[0].name == "prefix" - assert props.prefix_titles[1].name == "titles" - assert len(props.suffix_titles) == 2 - assert props.suffix_titles[0].name == "suffix" - assert props.suffix_titles[1].name == "titles" - - def test_overwriting_a_previous_import(self): - ifc = ifcopenshell.file() - tool.Ifc().set(ifc) - person = ifc.createIfcPerson() - person.Identification = "identification" - person.GivenName = "given_name" - subject().set_person(person) - subject().import_attributes() - person.Identification = "identification2" - person.GivenName = None - subject().import_attributes() - props = bpy.context.scene.BIMOwnerProperties - assert props.person_attributes.get("Identification").string_value == "identification2" - assert props.person_attributes.get("GivenName").string_value == "" - - -class TestClearPerson(test.bim.bootstrap.NewFile): - def test_run(self): - props = bpy.context.scene.BIMOwnerProperties - props.active_person_id = 1 - subject().clear_person() - assert props.active_person_id == 0 - - -class TestExportAttributes(test.bim.bootstrap.NewFile): - def test_run(self): - ifc = ifcopenshell.file() - tool.Ifc().set(ifc) - person = ifc.createIfcPerson() - person.Identification = "identification" - person.GivenName = "given_name" - person.FamilyName = "family_name" - person.MiddleNames = ("middle", "names") - person.PrefixTitles = ("prefix", "titles") - person.SuffixTitles = ("suffix", "titles") - subject().set_person(person) - subject().import_attributes() - assert subject().export_attributes() == { - "Identification": "identification", - "GivenName": "given_name", - "FamilyName": "family_name", - "MiddleNames": ["middle", "names"], - "PrefixTitles": ["prefix", "titles"], - "SuffixTitles": ["suffix", "titles"], - } - - def test_getting_empty_list_attributes_as_none(self): - ifc = ifcopenshell.file() - tool.Ifc().set(ifc) - result = subject().export_attributes() - assert result["MiddleNames"] is None - assert result["PrefixTitles"] is None - assert result["SuffixTitles"] is None - - -class TestGetPerson(test.bim.bootstrap.NewFile): - def test_run(self): - ifc = ifcopenshell.file() - tool.Ifc().set(ifc) - person = ifc.createIfcPerson() - subject().set_person(person) - assert subject().get_person() == person - - -class TestAddAttribute(test.bim.bootstrap.NewFile): - def test_run(self): - subject().add_attribute("MiddleNames") - subject().add_attribute("PrefixTitles") - subject().add_attribute("SuffixTitles") - props = bpy.context.scene.BIMOwnerProperties - assert len(props.middle_names) == 1 - assert len(props.prefix_titles) == 1 - assert len(props.suffix_titles) == 1 - - -class TestRemoveAttribute(test.bim.bootstrap.NewFile): - def test_run(self): - subject().add_attribute("MiddleNames") - subject().remove_attribute("MiddleNames", 0) - subject().add_attribute("PrefixTitles") - subject().remove_attribute("PrefixTitles", 0) - subject().add_attribute("SuffixTitles") - subject().remove_attribute("SuffixTitles", 0) - props = bpy.context.scene.BIMOwnerProperties - assert len(props.middle_names) == 0 - assert len(props.prefix_titles) == 0 - assert len(props.suffix_titles) == 0 diff --git a/src/blenderbim/test/tool/test_role_editor.py b/src/blenderbim/test/tool/test_role_editor.py deleted file mode 100644 index c9c77aab36..0000000000 --- a/src/blenderbim/test/tool/test_role_editor.py +++ /dev/null @@ -1,98 +0,0 @@ -# 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 test.bim.bootstrap -import blenderbim.core.tool -import blenderbim.tool as tool -from blenderbim.tool.role_editor import RoleEditor as subject - - -class TestImplementsTool(test.bim.bootstrap.NewFile): - def test_run(self): - assert isinstance(subject(), blenderbim.core.tool.RoleEditor) - - -class TestSetRole(test.bim.bootstrap.NewFile): - def test_run(self): - role = ifcopenshell.file().createIfcActorRole() - subject().set_role(role) - assert bpy.context.scene.BIMOwnerProperties.active_role_id == role.id() - - -class TestImportAttributes(test.bim.bootstrap.NewFile): - def test_run(self): - ifc = ifcopenshell.file() - tool.Ifc().set(ifc) - role = ifc.createIfcActorRole() - role.Role = "USERDEFINED" - role.UserDefinedRole = "UserDefinedRole" - role.Description = "Description" - subject().set_role(role) - subject().import_attributes() - props = bpy.context.scene.BIMOwnerProperties - assert props.role_attributes.get("Role").enum_value == "USERDEFINED" - assert props.role_attributes.get("UserDefinedRole").string_value == "UserDefinedRole" - assert props.role_attributes.get("Description").string_value == "Description" - - def test_importing_twice(self): - ifc = ifcopenshell.file() - tool.Ifc().set(ifc) - role = ifc.createIfcActorRole() - role.Role = "USERDEFINED" - subject().set_role(role) - subject().import_attributes() - role.Role = "ARCHITECT" - subject().import_attributes() - props = bpy.context.scene.BIMOwnerProperties - assert props.role_attributes.get("Role").enum_value == "ARCHITECT" - - -class TestClearRole(test.bim.bootstrap.NewFile): - def test_run(self): - role = ifcopenshell.file().createIfcActorRole() - subject().set_role(role) - subject().clear_role() - assert bpy.context.scene.BIMOwnerProperties.active_role_id == 0 - - -class TestGetRole(test.bim.bootstrap.NewFile): - def test_run(self): - ifc = ifcopenshell.file() - tool.Ifc().set(ifc) - role = ifc.createIfcActorRole() - subject().set_role(role) - assert subject().get_role() == role - - -class TestExportAttributes(test.bim.bootstrap.NewFile): - def test_run(self): - ifc = ifcopenshell.file() - tool.Ifc().set(ifc) - role = ifc.createIfcActorRole() - role.Role = "USERDEFINED" - role.UserDefinedRole = "UserDefinedRole" - role.Description = "Description" - subject().set_role(role) - subject().import_attributes() - assert subject().export_attributes() == { - "Role": "USERDEFINED", - "UserDefinedRole": "UserDefinedRole", - "Description": "Description", - } diff --git a/src/blenderbim/test/tool/test_style.py b/src/blenderbim/test/tool/test_style.py new file mode 100644 index 0000000000..029892026a --- /dev/null +++ b/src/blenderbim/test/tool/test_style.py @@ -0,0 +1,183 @@ +# 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 test.bim.bootstrap import NewFile +from blenderbim.tool.style import Style as subject + + +class TestImplementsTool(NewFile): + def test_run(self): + assert isinstance(subject(), blenderbim.core.tool.Style) + + +class TestDisableEditing(NewFile): + def test_run(self): + obj = bpy.data.materials.new("Material") + obj.BIMStyleProperties.is_editing = True + subject.disable_editing(obj) + assert obj.BIMStyleProperties.is_editing is False + + +class TestEnableEditing(NewFile): + def test_run(self): + obj = bpy.data.materials.new("Material") + subject.enable_editing(obj) + assert obj.BIMStyleProperties.is_editing is True + + +class TestExportSurfaceAttributes(NewFile): + def test_run(self): + TestImportSurfaceAttributes().test_run() + obj = bpy.data.materials.get("Material") + assert subject.export_surface_attributes(obj) == {"Name": "Name", "Side": "BOTH"} + + +class TestGetContext(NewFile): + def test_run(self): + bpy.ops.bim.create_project() + context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW") + assert subject.get_context("obj") == context + + +class TestGetName(NewFile): + def test_run(self): + assert subject.get_name(bpy.data.materials.new("Material")) == "Material" + + +class TestGetStyle(NewFile): + def test_getting_no_style(self): + assert subject.get_style(bpy.data.materials.new("Material")) is None + + def test_getting_a_linked_style(self): + tool.Ifc.set(ifcopenshell.file()) + style = tool.Ifc.get().createIfcSurfaceStyle() + obj = bpy.data.materials.new("Material") + obj.BIMMaterialProperties.ifc_style_id = style.id() + assert subject.get_style(obj) == style + + +class TestGetSurfaceRenderingAttributes(NewFile): + def test_get_colours_from_a_basic_material(self): + obj = bpy.data.materials.new("Material") + obj.diffuse_color = [1, 1, 1, 1] + assert subject.get_surface_rendering_attributes(obj) == { + "SurfaceColour": { + "Name": None, + "Red": 1, + "Green": 1, + "Blue": 1, + }, + "Transparency": 0, + "DiffuseColour": { + "Name": None, + "Red": 1, + "Green": 1, + "Blue": 1, + }, + } + + def test_get_different_surface_and_diffuse_colours_from_a_node_based_material(self): + obj = bpy.data.materials.new("Material") + obj.diffuse_color = [1, 1, 1, 1] + obj.use_nodes = True + node = obj.node_tree.nodes["Principled BSDF"] + node.inputs["Alpha"].default_value = 0.8 + node.inputs["Base Color"].default_value = [0.5, 0.5, 0.5, 0.5] + assert subject.get_surface_rendering_attributes(obj) == { + "SurfaceColour": { + "Name": None, + "Red": 1, + "Green": 1, + "Blue": 1, + }, + "Transparency": 1 - node.inputs["Alpha"].default_value, + "DiffuseColour": { + "Name": None, + "Red": 1, + "Green": 1, + "Blue": 1, + }, + } + + +class TestGetSurfaceRenderingStyle(NewFile): + def test_run(self): + tool.Ifc.set(ifcopenshell.file()) + style_item = tool.Ifc.get().createIfcSurfaceStyleRendering() + style = tool.Ifc.get().createIfcSurfaceStyle(Styles=[style_item]) + obj = bpy.data.materials.new("Material") + obj.BIMMaterialProperties.ifc_style_id = style.id() + assert subject.get_surface_rendering_style(obj) == style_item + + +class TestGetSurfaceShadingAttributes(NewFile): + def test_get_colours_from_a_basic_material(self): + obj = bpy.data.materials.new("Material") + obj.diffuse_color = [1, 1, 1, 1] + assert subject.get_surface_shading_attributes(obj) == { + "SurfaceColour": { + "Name": None, + "Red": 1, + "Green": 1, + "Blue": 1, + }, + "Transparency": 0, + } + + +class TestGetSurfaceShadingStyle(NewFile): + def test_run(self): + tool.Ifc.set(ifcopenshell.file()) + style_item = tool.Ifc.get().createIfcSurfaceStyleShading() + style = tool.Ifc.get().createIfcSurfaceStyle(Styles=[style_item]) + obj = bpy.data.materials.new("Material") + obj.BIMMaterialProperties.ifc_style_id = style.id() + assert subject.get_surface_shading_style(obj) == style_item + + +class TestImportSurfaceAttributes(NewFile): + def test_run(self): + ifc = ifcopenshell.file() + style = ifc.createIfcSurfaceStyle("Name", "BOTH") + obj = bpy.data.materials.new("Material") + subject.import_surface_attributes(style, obj) + assert obj.BIMStyleProperties.attributes.get("Name").string_value == "Name" + assert obj.BIMStyleProperties.attributes.get("Side").enum_value == "BOTH" + + +class TestLink(NewFile): + def test_run(self): + obj = bpy.data.materials.new("Material") + ifc = ifcopenshell.file() + style = ifc.createIfcSurfaceStyle() + subject.link(style, obj) + assert obj.BIMMaterialProperties.ifc_style_id == style.id() + + +class TestUnlink(NewFile): + def test_run(self): + obj = bpy.data.materials.new("Material") + ifc = ifcopenshell.file() + style = ifc.createIfcSurfaceStyle() + subject.link(style, obj) + subject.unlink(obj) + assert obj.BIMMaterialProperties.ifc_style_id == 0 diff --git a/src/ifcopenshell-python/ifcopenshell/api/style/add_style.py b/src/ifcopenshell-python/ifcopenshell/api/style/add_style.py index 7594462cbc..dcc09d0a13 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/style/add_style.py +++ b/src/ifcopenshell-python/ifcopenshell/api/style/add_style.py @@ -1,43 +1,10 @@ class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = { - "name": "Name", - "surface_colour": [], # RGB - "diffuse_colour": [], # RGB - "transparency": 0, - "external_definition": {"location": None, "identification": None, "name": "Name"}, - } + self.settings = {"name": None} for key, value in settings.items(): self.settings[key] = value def execute(self): - styles = [self.create_surface_style_rendering()] - if self.settings["external_definition"]: - styles.append(self.create_externally_defined_surface_style()) # Name is filled out because Revit treats this incorrectly as the material name - return self.file.createIfcSurfaceStyle(self.settings["name"], "BOTH", styles) - - def create_surface_style_rendering(self): - return self.file.create_entity( - "IfcSurfaceStyleRendering", - **{ - "SurfaceColour": self.create_colour_rgb(self.settings["surface_colour"]), - "Transparency": self.settings["transparency"], - "ReflectanceMethod": "NOTDEFINED", - "DiffuseColour": self.create_colour_rgb(self.settings["diffuse_colour"]), - } - ) - - def create_externally_defined_surface_style(self): - self.file.create_entity( - "IfcExternallyDefinedSurfaceStyle", - **{ - "Location": self.settings["location"], - "Identification": self.settings["identification"], - "Name": self.settings["name"], - } - ) - - def create_colour_rgb(self, colour): - return self.file.createIfcColourRgb(None, colour[0], colour[1], colour[2]) + return self.file.createIfcSurfaceStyle(self.settings["name"], "BOTH") diff --git a/src/ifcopenshell-python/ifcopenshell/api/style/add_surface_style.py b/src/ifcopenshell-python/ifcopenshell/api/style/add_surface_style.py new file mode 100644 index 0000000000..d975e6242e --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/style/add_surface_style.py @@ -0,0 +1,27 @@ +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = {"style": None, "ifc_class": "IfcSurfaceStyleRendering", "attributes": {}} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + for key, value in self.settings["attributes"].items(): + if key == "SurfaceColour" and value: + self.settings["attributes"][key] = self.create_colour_rgb(value) + if key == "DiffuseColour" and isinstance(value, dict): + self.settings["attributes"][key] = self.create_colour_rgb(value) + + style_item = self.file.create_entity(self.settings["ifc_class"], **self.settings["attributes"]) + styles = list(self.settings["style"].Styles or []) + + duplicate_items = [s for s in styles if s.is_a(self.settings["ifc_class"])] + for duplicate_item in duplicate_items: + self.file.remove(duplicate_item) + + styles.append(style_item) + self.settings["style"].Styles = styles + return style_item + + def create_colour_rgb(self, value): + return self.file.createIfcColourRgb(value["Name"], value["Red"], value["Green"], value["Blue"]) diff --git a/src/ifcopenshell-python/ifcopenshell/api/style/edit_surface_style.py b/src/ifcopenshell-python/ifcopenshell/api/style/edit_surface_style.py index 52ae3df70e..18209f3702 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/style/edit_surface_style.py +++ b/src/ifcopenshell-python/ifcopenshell/api/style/edit_surface_style.py @@ -15,9 +15,9 @@ class Usecase: setattr(self.settings["style"], key, value) def edit_surface_colour(self, value): - self.settings["style"].SurfaceColour[1] = value[0] - self.settings["style"].SurfaceColour[2] = value[1] - self.settings["style"].SurfaceColour[3] = value[2] + self.settings["style"].SurfaceColour[1] = value["Red"] + self.settings["style"].SurfaceColour[2] = value["Green"] + self.settings["style"].SurfaceColour[3] = value["Blue"] def is_colour_or_factor(self, name): return name in [ @@ -29,15 +29,15 @@ class Usecase: ] def edit_colour_or_factor(self, name, value): - if isinstance(value, (list, tuple)): + if isinstance(value, dict): attribute = getattr(self.settings["style"], name) if not attribute or not attribute.is_a("IfcColourRgb"): colour = self.file.createIfcColourRgb(None, 0, 0, 0) setattr(self.settings["style"], name, colour) attribute = getattr(self.settings["style"], name) - attribute[1] = value[0] - attribute[2] = value[1] - attribute[3] = value[2] + attribute[1] = value["Red"] + attribute[2] = value["Green"] + attribute[3] = value["Blue"] else: existing_value = getattr(self.settings["style"], name) if existing_value and existing_value.id():