diff --git a/src/blenderbim/Makefile b/src/blenderbim/Makefile index 3c389c4bb7..d12ebb9622 100644 --- a/src/blenderbim/Makefile +++ b/src/blenderbim/Makefile @@ -436,6 +436,7 @@ endif .PHONY: test test: make test-core + make test-tool make test-bim .PHONY: test-core @@ -446,6 +447,10 @@ test-core: test-bim: pytest test/bim +.PHONY: test-tool +test-tool: + pytest test/tool + .PHONY: qa qa: black . diff --git a/src/blenderbim/blenderbim/bim/module/owner/__init__.py b/src/blenderbim/blenderbim/bim/module/owner/__init__.py index e33cb27af8..26e154171f 100644 --- a/src/blenderbim/blenderbim/bim/module/owner/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/owner/__init__.py @@ -26,6 +26,8 @@ classes = ( operator.AddPerson, operator.EditPerson, operator.RemovePerson, + operator.AddPersonAttribute, + operator.RemovePersonAttribute, operator.EnableEditingOrganisation, operator.DisableEditingOrganisation, operator.AddOrganisation, @@ -39,10 +41,10 @@ classes = ( operator.EnableEditingAddress, operator.DisableEditingAddress, operator.AddAddress, + operator.AddAddressAttribute, + operator.RemoveAddressAttribute, operator.EditAddress, operator.RemoveAddress, - prop.Role, - prop.Address, prop.Person, prop.Organisation, prop.BIMOwnerProperties, diff --git a/src/blenderbim/blenderbim/bim/module/owner/data.py b/src/blenderbim/blenderbim/bim/module/owner/data.py new file mode 100644 index 0000000000..b2f4f6dc91 --- /dev/null +++ b/src/blenderbim/blenderbim/bim/module/owner/data.py @@ -0,0 +1,129 @@ +# 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 + + +class PeopleData: + data = {} + is_loaded = False + + @classmethod + def load(cls): + cls.data = {"people": cls.get_people()} + cls.is_loaded = True + + @classmethod + def get_people(cls): + people = [] + for person in tool.Ifc().get().by_type("IfcPerson"): + people.append( + { + "id": person.id(), + "props": bpy.context.scene.BIMOwnerProperties.person_attributes, + "name": cls.get_person_name(person), + "is_editing": cls.get_person_is_editing(person), + "is_engaged": bool(person.EngagedIn), + "list_attributes": cls.get_person_list_attributes(person), + "roles": cls.get_person_roles(person), + "addresses": cls.get_person_addresses(person), + } + ) + return people + + @classmethod + def get_person_name(cls, person): + if tool.Ifc().get_schema() == "IFC2X3": + name = person.Id + else: + name = person.Identification + name = name or "*" + if person.GivenName or person.FamilyName: + full_name = "{} {}".format(person.GivenName or "", person.FamilyName or "").strip() + name += f" ({full_name})" + return name + + @classmethod + def get_person_is_editing(cls, person): + return bpy.context.scene.BIMOwnerProperties.active_person_id == person.id() + + @classmethod + def get_person_list_attributes(cls, person): + results = [] + props = bpy.context.scene.BIMOwnerProperties + for name in ["MiddleNames", "PrefixTitles", "SuffixTitles"]: + if name == "MiddleNames": + items = [{"id": id, "prop": prop} for id, prop in enumerate(props.middle_names)] + elif name == "PrefixTitles": + items = [{"id": id, "prop": prop} for id, prop in enumerate(props.prefix_titles)] + elif name == "SuffixTitles": + items = [{"id": id, "prop": prop} for id, prop in enumerate(props.suffix_titles)] + results.append({"name": name, "items": items}) + return results + + @classmethod + def get_person_roles(cls, person): + results = [] + for role in person.Roles or []: + results.append( + { + "id": role.id(), + "is_editing": bpy.context.scene.BIMOwnerProperties.active_role_id == role.id(), + "label": role.UserDefinedRole or role.Role, + "props": bpy.context.scene.BIMOwnerProperties.role_attributes, + } + ) + return results + + @classmethod + def get_person_addresses(cls, person): + results = [] + for address in person.Addresses or []: + results.append( + { + "id": address.id(), + "is_editing": bpy.context.scene.BIMOwnerProperties.active_address_id == address.id(), + "label": address.is_a(), + "props": bpy.context.scene.BIMOwnerProperties.address_attributes, + "list_attributes": cls.get_address_list_attributes(address), + } + ) + return results + + @classmethod + def get_address_list_attributes(cls, address): + results = [] + props = bpy.context.scene.BIMOwnerProperties + if address.is_a("IfcPostalAddress"): + names = ["AddressLines"] + elif address.is_a("IfcTelecomAddress"): + names = ["TelephoneNumbers", "FacsimileNumbers", "ElectronicMailAddresses", "MessagingIDs"] + for name in names: + if name == "AddressLines": + items = [{"id": id, "prop": prop} for id, prop in enumerate(props.address_lines)] + elif name == "TelephoneNumbers": + items = [{"id": id, "prop": prop} for id, prop in enumerate(props.telephone_numbers)] + elif name == "FacsimileNumbers": + items = [{"id": id, "prop": prop} for id, prop in enumerate(props.facsimile_numbers)] + elif name == "ElectronicMailAddresses": + items = [{"id": id, "prop": prop} for id, prop in enumerate(props.electronic_mail_addresses)] + elif name == "MessagingIDs": + items = [{"id": id, "prop": prop} for id, prop in enumerate(props.messaging_ids)] + results.append({"name": name, "items": items}) + return results diff --git a/src/blenderbim/blenderbim/bim/module/owner/operator.py b/src/blenderbim/blenderbim/bim/module/owner/operator.py index 9c5eb5c3b1..f1309afafe 100644 --- a/src/blenderbim/blenderbim/bim/module/owner/operator.py +++ b/src/blenderbim/blenderbim/bim/module/owner/operator.py @@ -18,10 +18,21 @@ import bpy import ifcopenshell.api +import blenderbim.tool as tool +import blenderbim.core.owner as core +import blenderbim.bim.module.owner.data from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.owner.data import Data +# TODO: Just testing +class Operator: + def execute(self, context): + IfcStore.execute_ifc_operator(self, context) + blenderbim.bim.module.owner.data.PeopleData.is_loaded = False + return {"FINISHED"} + + def flatten_collection(collection): return [v.name for v in collection] if collection else None @@ -59,317 +70,190 @@ class AddOrRemoveElementFromCollection(bpy.types.Operator): return {"FINISHED"} -class EnableEditingPerson(bpy.types.Operator): +class EnableEditingPerson(bpy.types.Operator, Operator): bl_idname = "bim.enable_editing_person" bl_label = "Enable Editing Person" bl_options = {"REGISTER", "UNDO"} - person_id: bpy.props.IntProperty() + person: bpy.props.IntProperty() - def execute(self, context): - self.file = IfcStore.get_file() - props = context.scene.BIMOwnerProperties - props.active_person_id = self.person_id - data = Data.people[self.person_id] - name = data["Id"] if self.file.schema == "IFC2X3" else data["Identification"] - person = props.person - person.name = name or "" - person.family_name = data["FamilyName"] or "" - person.given_name = data["GivenName"] or "" - populate_collection(person.middle_names, data.get("MiddleNames", None)) - populate_collection(person.prefix_titles, data.get("PrefixTitles", None)) - populate_collection(person.suffix_titles, data.get("SuffixTitles", None)) - return {"FINISHED"} + def _execute(self, context): + core.enable_editing_person(tool.PersonEditor(), person=tool.Ifc().get().by_id(self.person)) -class DisableEditingPerson(bpy.types.Operator): +class DisableEditingPerson(bpy.types.Operator, Operator): bl_idname = "bim.disable_editing_person" bl_label = "Disable Editing Person" bl_options = {"REGISTER", "UNDO"} - def execute(self, context): - context.scene.BIMOwnerProperties.active_person_id = 0 - return {"FINISHED"} + def _execute(self, context): + core.disable_editing_person(tool.PersonEditor()) -class AddPerson(bpy.types.Operator): +class AddPerson(bpy.types.Operator, Operator): bl_idname = "bim.add_person" bl_label = "Add Person" bl_options = {"REGISTER", "UNDO"} - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - ifcopenshell.api.run("owner.add_person", IfcStore.get_file()) - Data.load(IfcStore.get_file()) - return {"FINISHED"} + core.add_person(tool.Ifc()) -class EditPerson(bpy.types.Operator): +class EditPerson(bpy.types.Operator, Operator): bl_idname = "bim.edit_person" bl_label = "Edit Person" bl_options = {"REGISTER", "UNDO"} - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - self.file = IfcStore.get_file() - props = context.scene.BIMOwnerProperties - person = props.person - attributes = { - "Identification": person.name or None, - "FamilyName": person.family_name or None, - "GivenName": person.given_name or None, - "MiddleNames": flatten_collection(person.middle_names), - "PrefixTitles": flatten_collection(person.prefix_titles), - "SuffixTitles": flatten_collection(person.suffix_titles), - } - if self.file.schema == "IFC2X3": - attributes["Id"] = attributes["Identification"] - del attributes["Identification"] - ifcopenshell.api.run( - "owner.edit_person", - self.file, - **{"person": self.file.by_id(props.active_person_id), "attributes": attributes} - ) - Data.load(IfcStore.get_file()) - bpy.ops.bim.disable_editing_person() - return {"FINISHED"} + core.edit_person(tool.Ifc(), tool.PersonEditor()) -class RemovePerson(bpy.types.Operator): +class RemovePerson(bpy.types.Operator, Operator): bl_idname = "bim.remove_person" bl_label = "Remove Person" bl_options = {"REGISTER", "UNDO"} - person_id: bpy.props.IntProperty() - - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) + person: bpy.props.IntProperty() def _execute(self, context): - self.file = IfcStore.get_file() - ifcopenshell.api.run("owner.remove_person", self.file, **{"person": self.file.by_id(self.person_id)}) - Data.load(IfcStore.get_file()) - return {"FINISHED"} + core.remove_person(tool.Ifc(), person=tool.Ifc().get().by_id(self.person)) -class EnableEditingRole(bpy.types.Operator): +class AddPersonAttribute(bpy.types.Operator, Operator): + bl_idname = "bim.add_person_attribute" + bl_label = "Add Person Attribute" + bl_options = {"REGISTER", "UNDO"} + name: bpy.props.StringProperty() + + def _execute(self, context): + core.add_person_attribute(tool.PersonEditor(), name=self.name) + + +class RemovePersonAttribute(bpy.types.Operator, Operator): + bl_idname = "bim.remove_person_attribute" + bl_label = "Remove Person Attribute" + bl_options = {"REGISTER", "UNDO"} + name: bpy.props.StringProperty() + id: bpy.props.IntProperty() + + def _execute(self, context): + core.remove_person_attribute(tool.PersonEditor(), name=self.name, id=self.id) + + +class EnableEditingRole(bpy.types.Operator, Operator): bl_idname = "bim.enable_editing_role" bl_label = "Enable Editing Role" bl_options = {"REGISTER", "UNDO"} - role_id: bpy.props.IntProperty() + role: bpy.props.IntProperty() - def execute(self, context): - self.file = IfcStore.get_file() - props = context.scene.BIMOwnerProperties - props.active_role_id = self.role_id - data = Data.roles[self.role_id] - props.role.name = data["Role"] - props.role.user_defined_role = data["UserDefinedRole"] or "" - props.role.description = data["Description"] or "" - return {"FINISHED"} + def _execute(self, context): + core.enable_editing_role(tool.RoleEditor(), role=tool.Ifc().get().by_id(self.role)) -class DisableEditingRole(bpy.types.Operator): +class DisableEditingRole(bpy.types.Operator, Operator): bl_idname = "bim.disable_editing_role" bl_label = "Disable Editing Role" bl_options = {"REGISTER", "UNDO"} - def execute(self, context): - context.scene.BIMOwnerProperties.active_role_id = 0 - return {"FINISHED"} + def _execute(self, context): + core.disable_editing_role(tool.RoleEditor()) -class AddRole(bpy.types.Operator): +class AddRole(bpy.types.Operator, Operator): bl_idname = "bim.add_role" bl_label = "Add Role" bl_options = {"REGISTER", "UNDO"} - assigned_object_id: bpy.props.IntProperty() - - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) + parent: bpy.props.IntProperty() def _execute(self, context): - self.file = IfcStore.get_file() - ifcopenshell.api.run( - "owner.add_role", self.file, **{"assigned_object": self.file.by_id(self.assigned_object_id)} - ) - Data.load(IfcStore.get_file()) - return {"FINISHED"} + core.add_role(tool.Ifc(), parent=tool.Ifc().get().by_id(self.parent)) -class EditRole(bpy.types.Operator): +class EditRole(bpy.types.Operator, Operator): bl_idname = "bim.edit_role" bl_label = "Edit Role" bl_options = {"REGISTER", "UNDO"} - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - self.file = IfcStore.get_file() - props = context.scene.BIMOwnerProperties - attributes = { - "Role": props.role.name, - "UserDefinedRole": props.role.user_defined_role if props.role.name == "USERDEFINED" else None, - "Description": props.role.description or None, - } - ifcopenshell.api.run( - "owner.edit_role", self.file, **{"role": self.file.by_id(props.active_role_id), "attributes": attributes} - ) - Data.load(IfcStore.get_file()) - bpy.ops.bim.disable_editing_role() - return {"FINISHED"} + core.edit_role(tool.Ifc(), tool.RoleEditor()) -class RemoveRole(bpy.types.Operator): +class RemoveRole(bpy.types.Operator, Operator): bl_idname = "bim.remove_role" bl_label = "Remove Role" bl_options = {"REGISTER", "UNDO"} - role_id: bpy.props.IntProperty() - - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) + role: bpy.props.IntProperty() def _execute(self, context): - self.file = IfcStore.get_file() - ifcopenshell.api.run("owner.remove_role", self.file, **{"role": self.file.by_id(self.role_id)}) - Data.load(IfcStore.get_file()) - return {"FINISHED"} + core.remove_role(tool.Ifc(), role=tool.Ifc().get().by_id(self.role)) -class AddAddress(bpy.types.Operator): +class AddAddress(bpy.types.Operator, Operator): bl_idname = "bim.add_address" bl_label = "Add Address" bl_options = {"REGISTER", "UNDO"} - assigned_object_id: bpy.props.IntProperty() + parent: bpy.props.IntProperty() ifc_class: bpy.props.StringProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) + def _execute(self, context): + core.add_address(tool.Ifc(), parent=tool.Ifc().get().by_id(self.parent), ifc_class=self.ifc_class) + + +class AddAddressAttribute(bpy.types.Operator, Operator): + bl_idname = "bim.add_address_attribute" + bl_label = "Add Address Attribute" + bl_options = {"REGISTER", "UNDO"} + name: bpy.props.StringProperty() def _execute(self, context): - self.file = IfcStore.get_file() - ifcopenshell.api.run( - "owner.add_address", - self.file, - **{"assigned_object": self.file.by_id(self.assigned_object_id), "ifc_class": self.ifc_class} - ) - Data.load(IfcStore.get_file()) - return {"FINISHED"} + core.add_address_attribute(tool.AddressEditor, name=self.name) -class EnableEditingAddress(bpy.types.Operator): +class RemoveAddressAttribute(bpy.types.Operator, Operator): + bl_idname = "bim.remove_address_attribute" + bl_label = "Remove Address Attribute" + bl_options = {"REGISTER", "UNDO"} + name: bpy.props.StringProperty() + id: bpy.props.IntProperty() + + def _execute(self, context): + core.remove_address_attribute(tool.AddressEditor, name=self.name, id=self.id) + + +class EnableEditingAddress(bpy.types.Operator, Operator): bl_idname = "bim.enable_editing_address" bl_label = "Enable Editing Address" bl_options = {"REGISTER", "UNDO"} - address_id: bpy.props.IntProperty() + address: bpy.props.IntProperty() - def execute(self, context): - self.file = IfcStore.get_file() - props = context.scene.BIMOwnerProperties - props.active_address_id = self.address_id - data = Data.addresses[self.address_id] - address = props.address - address.name = data["type"] - address.purpose = data["Purpose"] or "None" - address.description = data["Description"] or "" - address.user_defined_purpose = data["UserDefinedPurpose"] or "" - - if data["type"] == "IfcTelecomAddress": - populate_collection(address.telephone_numbers, data.get("TelephoneNumbers", None)) - populate_collection(address.facsimile_numbers, data.get("FacsimileNumbers", None)) - address.pager_number = data["PagerNumber"] or "" - populate_collection(address.electronic_mail_addresses, data.get("ElectronicMailAddresses", None)) - address.www_home_page_url = data["WWWHomePageURL"] or "" - if self.file.schema != "IFC2X3": - populate_collection(address.messaging_ids, data.get("MessagingIDs", None)) - elif data["type"] == "IfcPostalAddress": - address.internal_location = data["InternalLocation"] or "" - populate_collection(address.address_lines, data.get("AddressLines", None)) - address.postal_box = data["PostalBox"] or "" - address.town = data["Town"] or "" - address.region = data["Region"] or "" - address.postal_code = data["PostalCode"] or "" - address.country = data["Country"] or "" - return {"FINISHED"} + def _execute(self, context): + core.enable_editing_address(tool.AddressEditor(), address=tool.Ifc().get().by_id(self.address)) -class DisableEditingAddress(bpy.types.Operator): +class DisableEditingAddress(bpy.types.Operator, Operator): bl_idname = "bim.disable_editing_address" bl_label = "Disable Editing Address" bl_options = {"REGISTER", "UNDO"} - def execute(self, context): - context.scene.BIMOwnerProperties.active_address_id = 0 - return {"FINISHED"} + def _execute(self, context): + core.disable_editing_address(tool.AddressEditor()) -class EditAddress(bpy.types.Operator): +class EditAddress(bpy.types.Operator, Operator): bl_idname = "bim.edit_address" bl_label = "Edit Address" bl_options = {"REGISTER", "UNDO"} - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): - self.file = IfcStore.get_file() - props = context.scene.BIMOwnerProperties - attributes = { - "Purpose": props.address.purpose, - "UserDefinedPurpose": props.address.user_defined_purpose - if props.address.purpose == "USERDEFINED" - else None, - "Description": props.address.description or None, - } - address = self.file.by_id(props.active_address_id) - if address.is_a("IfcTelecomAddress"): - attributes.update( - { - "TelephoneNumbers": flatten_collection(props.address.telephone_numbers), - "FacsimileNumbers": flatten_collection(props.address.facsimile_numbers), - "PagerNumber": props.address.pager_number or None, - "ElectronicMailAddresses": flatten_collection(props.address.electronic_mail_addresses), - "WWWHomePageURL": props.address.www_home_page_url or None, - "MessagingIDs": flatten_collection(props.address.messaging_ids), - } - ) - if self.file.schema == "IFC2X3": - del attributes["MessagingIDs"] - elif address.is_a("IfcPostalAddress"): - attributes.update( - { - "InternalLocation": props.address.internal_location or None, - "AddressLines": flatten_collection(props.address.address_lines), - "PostalBox": props.address.postal_box or None, - "Town": props.address.town or None, - "Region": props.address.region or None, - "PostalCode": props.address.postal_code or None, - "Country": props.address.country or None, - } - ) - ifcopenshell.api.run("owner.edit_address", self.file, **{"address": address, "attributes": attributes}) - Data.load(IfcStore.get_file()) - bpy.ops.bim.disable_editing_address() - return {"FINISHED"} + core.edit_address(tool.Ifc, tool.AddressEditor) -class RemoveAddress(bpy.types.Operator): +class RemoveAddress(bpy.types.Operator, Operator): bl_idname = "bim.remove_address" bl_label = "Remove Address" bl_options = {"REGISTER", "UNDO"} - address_id: bpy.props.IntProperty() - - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) + address: bpy.props.IntProperty() def _execute(self, context): - self.file = IfcStore.get_file() - ifcopenshell.api.run("owner.remove_address", self.file, **{"address": self.file.by_id(self.address_id)}) - Data.load(IfcStore.get_file()) - return {"FINISHED"} + core.remove_address(tool.Ifc(), address=tool.Ifc().get().by_id(self.address)) class EnableEditingOrganisation(bpy.types.Operator): diff --git a/src/blenderbim/blenderbim/bim/module/owner/prop.py b/src/blenderbim/blenderbim/bim/module/owner/prop.py index 867df5e021..1acf5c36a1 100644 --- a/src/blenderbim/blenderbim/bim/module/owner/prop.py +++ b/src/blenderbim/blenderbim/bim/module/owner/prop.py @@ -67,71 +67,6 @@ def getOrganisations(self, context): return _organisations_enum -class Address(PropertyGroup): - name: StringProperty(name="Name", default="IfcPostalAddress") # Stores IfcPostalAddress or IfcTelecomAddress - purpose: EnumProperty( - items=[ - ("None", "None", ""), - ("OFFICE", "OFFICE", "An office address."), - ("SITE", "SITE", "A site address."), - ("HOME", "HOME", "A home address."), - ("DISTRIBUTIONPOINT", "DISTRIBUTIONPOINT", "A postal distribution point address."), - ("USERDEFINED", "USERDEFINED", "A user defined address type to be provided."), - ], - name="Purpose", - ) - description: StringProperty(name="Description") - user_defined_purpose: StringProperty(name="Custom Purpose") - - internal_location: StringProperty(name="Internal Location") - address_lines: CollectionProperty(type=StrProperty, name="Address") - postal_box: StringProperty(name="Postal Box") - town: StringProperty(name="Town") - region: StringProperty(name="Region") - postal_code: StringProperty(name="Postal Code") - country: StringProperty(name="Country") - - telephone_numbers: CollectionProperty(type=StrProperty, name="Telephone Numbers") - facsimile_numbers: CollectionProperty(type=StrProperty, name="Facsimile Numbers") - pager_number: StringProperty(name="Pager Number") - electronic_mail_addresses: CollectionProperty(type=StrProperty, name="Emails") - www_home_page_url: StringProperty(name="Website") - messaging_ids: CollectionProperty(type=StrProperty, name="IMs") - - -class Role(PropertyGroup): - name: EnumProperty( - items=[ - ("SUPPLIER", "SUPPLIER", ""), - ("MANUFACTURER", "MANUFACTURER", ""), - ("CONTRACTOR", "CONTRACTOR", ""), - ("SUBCONTRACTOR", "SUBCONTRACTOR", ""), - ("ARCHITECT", "ARCHITECT", ""), - ("STRUCTURALENGINEER", "STRUCTURALENGINEER", ""), - ("COSTENGINEER", "COSTENGINEER", ""), - ("CLIENT", "CLIENT", ""), - ("BUILDINGOWNER", "BUILDINGOWNER", ""), - ("BUILDINGOPERATOR", "BUILDINGOPERATOR", ""), - ("MECHANICALENGINEER", "MECHANICALENGINEER", ""), - ("ELECTRICALENGINEER", "ELECTRICALENGINEER", ""), - ("PROJECTMANAGER", "PROJECTMANAGER", ""), - ("FACILITIESMANAGER", "FACILITIESMANAGER", ""), - ("CIVILENGINEER", "CIVILENGINEER", ""), - ("COMMISSIONINGENGINEER", "COMMISSIONINGENGINEER", ""), - ("ENGINEER", "ENGINEER", ""), - ("OWNER", "OWNER", ""), - ("CONSULTANT", "CONSULTANT", ""), - ("CONSTRUCTIONMANAGER", "CONSTRUCTIONMANAGER", ""), - ("FIELDCONSTRUCTIONMANAGER", "FIELDCONSTRUCTIONMANAGER", ""), - ("RESELLER", "RESELLER", ""), - ("USERDEFINED", "USERDEFINED", ""), - ], - name="Name", - ) - user_defined_role: StringProperty(name="Custom Role") - description: StringProperty(name="Description") - - class Organisation(PropertyGroup): identification: StringProperty(name="Identification") name: StringProperty(name="Name") @@ -149,12 +84,21 @@ class Person(PropertyGroup): class BIMOwnerProperties(PropertyGroup): person: PointerProperty(type=Person) + person_attributes: CollectionProperty(name="Person Attributes", type=Attribute) + middle_names: CollectionProperty(type=StrProperty, name="Middle Names") + prefix_titles: CollectionProperty(type=StrProperty, name="Prefixes") + suffix_titles: CollectionProperty(type=StrProperty, name="Suffixes") active_person_id: IntProperty(name="Active Person Id") organisation: PointerProperty(type=Organisation) active_organisation_id: IntProperty(name="Active Organisation Id") - role: PointerProperty(type=Role) active_role_id: IntProperty(name="Active Role Id") - address: PointerProperty(type=Address) + role_attributes: CollectionProperty(name="Role Attributes", type=Attribute) active_address_id: IntProperty(name="Active Address Id") + address_attributes: CollectionProperty(name="Address Attributes", type=Attribute) + address_lines: CollectionProperty(type=StrProperty, name="Address") + telephone_numbers: CollectionProperty(type=StrProperty, name="Telephone Numbers") + facsimile_numbers: CollectionProperty(type=StrProperty, name="Facsimile Numbers") + electronic_mail_addresses: CollectionProperty(type=StrProperty, name="Emails") + messaging_ids: CollectionProperty(type=StrProperty, name="IMs") user_person: EnumProperty(items=getPersons, name="Person") user_organisation: EnumProperty(items=getOrganisations, name="Organisation") diff --git a/src/blenderbim/blenderbim/bim/module/owner/ui.py b/src/blenderbim/blenderbim/bim/module/owner/ui.py index 42565c302e..f4a68ea92f 100644 --- a/src/blenderbim/blenderbim/bim/module/owner/ui.py +++ b/src/blenderbim/blenderbim/bim/module/owner/ui.py @@ -17,10 +17,12 @@ # along with BlenderBIM Add-on. If not, see . import bpy +import blenderbim.bim.helper +import blenderbim.tool as tool +from blenderbim.bim.module.owner.data import PeopleData from bpy.types import Panel from ifcopenshell.api.owner.data import Data from blenderbim.bim.ifc import IfcStore -from .operator import AddOrRemoveElementFromCollection def draw_string_collection(layout, owner, collection_name): @@ -31,12 +33,12 @@ def draw_string_collection(layout, owner, collection_name): row = draw_prop_on_new_row( column, collection[i], "name", align=True, text=f"{owner.bl_rna.properties[collection_name].name}" ) - add_op = row.operator(AddOrRemoveElementFromCollection.bl_idname, icon="ADD", text="") + add_op = row.operator("bim.add_or_remove_element_from_collection", icon="ADD", text="") add_op.operation = "+" add_op.collection_path = collection.path_from_id() else: row = draw_prop_on_new_row(column, collection[i], "name", align=True, text=f"#{i + 1}") - rem_op = row.operator(AddOrRemoveElementFromCollection.bl_idname, icon="REMOVE", text="") + rem_op = row.operator("bim.add_or_remove_element_from_collection", icon="REMOVE", text="") rem_op.operation = "-" rem_op.collection_path = collection.path_from_id() rem_op.selected_item_idx = i @@ -116,7 +118,7 @@ def draw_addresses_ui(box, assigned_object_id, addresses, file, context): row.operator("bim.remove_address", icon="X", text="").address_id = address_id -class BIM_PT_people(Panel): +class BIM_PT_people(bpy.types.Panel): bl_label = "IFC People" bl_idname = "BIM_PT_people" bl_options = {"DEFAULT_CLOSED"} @@ -126,47 +128,102 @@ class BIM_PT_people(Panel): @classmethod def poll(cls, context): - return IfcStore.get_file() + return tool.Ifc().get() def draw(self, context): - if not Data.is_loaded: - Data.load(IfcStore.get_file()) + if not PeopleData.is_loaded: + PeopleData.load() - self.file = IfcStore.get_file() self.layout.use_property_split = True self.layout.use_property_decorate = False - props = context.scene.BIMOwnerProperties row = self.layout.row() row.operator("bim.add_person", icon="ADD") - for person_id, person in Data.people.items(): - if props.active_person_id == person_id: - blender_person = props.person - box = self.layout.box() - row = draw_prop_on_new_row(box, blender_person, "name", align=True, icon="USER", text="") - row.operator("bim.edit_person", icon="CHECKMARK", text="") - row.operator("bim.disable_editing_person", icon="CANCEL", text="") - draw_prop_on_new_row(box, blender_person, "family_name") - draw_prop_on_new_row(box, blender_person, "given_name") - draw_string_collection(box, blender_person, "middle_names") - draw_string_collection(box, blender_person, "prefix_titles") - draw_string_collection(box, blender_person, "suffix_titles") - draw_roles_ui(box, person_id, person["Roles"], context) - draw_addresses_ui(box, person_id, person["Addresses"], self.file, context) + for person in PeopleData.data["people"]: + self.draw_person(person) + + def draw_person(self, person): + if person["is_editing"]: + box = self.layout.box() + row = box.row(align=True) + row.operator("bim.edit_person", icon="CHECKMARK") + row.operator("bim.disable_editing_person", icon="CANCEL", text="") + blenderbim.bim.helper.draw_attributes(person["props"], box) + + for attribute in person["list_attributes"]: + row = box.row(align=True) + row.label(text=attribute["name"]) + op = row.operator("bim.add_person_attribute", icon="ADD", text="") + op.name = attribute["name"] + + for item in attribute["items"]: + row = box.row(align=True) + row.prop(item["prop"], "name", text="") + op = row.operator("bim.remove_person_attribute", icon="REMOVE", text="") + op.name = attribute["name"] + op.id = item["id"] + + self.draw_roles(box, person) + self.draw_addresses(box, person) + else: + row = self.layout.row(align=True) + row.label(text=person["name"]) + row.operator("bim.enable_editing_person", icon="GREASEPENCIL", text="").person = person["id"] + if not person["is_engaged"]: + row.operator("bim.remove_person", icon="X", text="").person = person["id"] + + def draw_roles(self, box, person): + row = box.row(align=True) + row.label(text="Roles") + op = row.operator("bim.add_role", icon="ADD", text="") + op.parent = person["id"] + + for role in person["roles"]: + if role["is_editing"]: + row = box.row(align=True) + row.operator("bim.edit_role", icon="CHECKMARK") + row.operator("bim.disable_editing_role", icon="CANCEL", text="") + blenderbim.bim.helper.draw_attributes(role["props"], box) else: - row = self.layout.row(align=True) - name = person["Id"] if self.file.schema == "IFC2X3" else person["Identification"] - name = name or "*" - if person["GivenName"] or person["FamilyName"]: - full_name = "{} {}".format(person["GivenName"] or "", person["FamilyName"] or "").strip() - name += f" ({full_name})" - row.label(text=name) - if person["Roles"]: - row.label(text=", ".join([Data.roles[r]["Role"] for r in person["Roles"]])) - row.operator("bim.enable_editing_person", icon="GREASEPENCIL", text="").person_id = person_id - if not person["is_engaged"]: - row.operator("bim.remove_person", icon="X", text="").person_id = person_id + row = box.row(align=True) + row.label(text=role["label"]) + row.operator("bim.enable_editing_role", icon="GREASEPENCIL", text="").role = role["id"] + row.operator("bim.remove_role", icon="X", text="").role = role["id"] + + def draw_addresses(self, box, person): + row = box.row(align=True) + row.label(text="Addresses") + op = row.operator("bim.add_address", icon="LINK_BLEND", text="") + op.parent = person["id"] + op.ifc_class = "IfcTelecomAddress" + op = row.operator("bim.add_address", icon="APPEND_BLEND", text="") + op.parent = person["id"] + op.ifc_class = "IfcPostalAddress" + + for address in person["addresses"]: + if address["is_editing"]: + row = box.row(align=True) + row.operator("bim.edit_address", icon="CHECKMARK") + row.operator("bim.disable_editing_address", icon="CANCEL", text="") + blenderbim.bim.helper.draw_attributes(address["props"], box) + for attribute in address["list_attributes"]: + row = box.row(align=True) + row.label(text=attribute["name"]) + op = row.operator("bim.add_address_attribute", icon="ADD", text="") + op.name = attribute["name"] + + for item in attribute["items"]: + row = box.row(align=True) + row.prop(item["prop"], "name", text="") + op = row.operator("bim.remove_address_attribute", icon="REMOVE", text="") + op.name = attribute["name"] + op.id = item["id"] + else: + row = box.row(align=True) + row.label(text=address["label"]) + row.operator("bim.enable_editing_address", icon="GREASEPENCIL", text="").address = address["id"] + row.operator("bim.remove_address", icon="X", text="").address = address["id"] class BIM_PT_organisations(Panel): diff --git a/src/blenderbim/blenderbim/bim/module/pset_template/operator.py b/src/blenderbim/blenderbim/bim/module/pset_template/operator.py index 1628522e02..8699f6d327 100644 --- a/src/blenderbim/blenderbim/bim/module/pset_template/operator.py +++ b/src/blenderbim/blenderbim/bim/module/pset_template/operator.py @@ -21,11 +21,7 @@ import ifcopenshell import ifcopenshell.api import blenderbim.bim.schema import blenderbim.bim.handler -from blenderbim.bim.module.pset_template.prop import ( - updatePsetTemplateFiles, - updatePsetTemplates, - getPsetTemplates -) +from blenderbim.bim.module.pset_template.prop import updatePsetTemplateFiles, updatePsetTemplates, getPsetTemplates from ifcopenshell.api.pset_template.data import Data from blenderbim.bim.ifc import IfcStore diff --git a/src/blenderbim/blenderbim/core/owner.py b/src/blenderbim/blenderbim/core/owner.py new file mode 100644 index 0000000000..d30e5c3cee --- /dev/null +++ b/src/blenderbim/blenderbim/core/owner.py @@ -0,0 +1,100 @@ +# 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_person(ifc): + return ifc.run("owner.add_person") + + +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 disable_editing_person(person_editor): + person_editor.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 add_person_attribute(person_editor, name=None): + person_editor.add_attribute(name) + + +def remove_person_attribute(person_editor, name=None, id=None): + person_editor.remove_attribute(name, id) + + +def add_role(ifc, parent=None): + return ifc.run("owner.add_role", assigned_object=parent) + + +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 disable_editing_role(role_editor): + role_editor.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 add_address(ifc, parent=None, ifc_class="IfcPostalAddress"): + return ifc.run("owner.add_address", assigned_object=parent, ifc_class=ifc_class) + + +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 disable_editing_address(address_editor): + address_editor.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 add_address_attribute(address_editor, name=None): + address_editor.add_attribute(name) + + +def remove_address_attribute(address_editor, name=None, id=None): + address_editor.remove_attribute(name, id) diff --git a/src/blenderbim/blenderbim/core/tool/__init__.py b/src/blenderbim/blenderbim/core/tool/__init__.py new file mode 100644 index 0000000000..09f78918e7 --- /dev/null +++ b/src/blenderbim/blenderbim/core/tool/__init__.py @@ -0,0 +1,23 @@ +# 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 . + +from blenderbim.core.tool.ifc import Ifc +from blenderbim.core.tool.blender import Blender +from blenderbim.core.tool.person_editor import PersonEditor +from blenderbim.core.tool.role_editor import RoleEditor +from blenderbim.core.tool.address_editor import AddressEditor diff --git a/src/blenderbim/blenderbim/core/tool/address_editor.py b/src/blenderbim/blenderbim/core/tool/address_editor.py new file mode 100644 index 0000000000..b845b79119 --- /dev/null +++ b/src/blenderbim/blenderbim/core/tool/address_editor.py @@ -0,0 +1,56 @@ +# 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 abc + + +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 diff --git a/src/blenderbim/blenderbim/core/tool/blender.py b/src/blenderbim/blenderbim/core/tool/blender.py new file mode 100644 index 0000000000..a061e9112e --- /dev/null +++ b/src/blenderbim/blenderbim/core/tool/blender.py @@ -0,0 +1,23 @@ +# 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 abc + + +class Blender(abc.ABC): + pass diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool/ifc.py similarity index 86% rename from src/blenderbim/blenderbim/core/tool.py rename to src/blenderbim/blenderbim/core/tool/ifc.py index c5bca78893..07c59144b4 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool/ifc.py @@ -16,15 +16,11 @@ # You should have received a copy of the GNU General Public License # along with BlenderBIM Add-on. If not, see . +import abc -class Ifc: + +class Ifc(abc.ABC): @classmethod + @abc.abstractmethod def run(cls, command, **kwargs): - """Runs an operation on the active IFC dataset""" - pass - - -class Blender: - @classmethod - def get_ifc(cls): pass diff --git a/src/blenderbim/blenderbim/core/tool/person_editor.py b/src/blenderbim/blenderbim/core/tool/person_editor.py new file mode 100644 index 0000000000..bd2bce4906 --- /dev/null +++ b/src/blenderbim/blenderbim/core/tool/person_editor.py @@ -0,0 +1,56 @@ +# 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 abc + + +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 diff --git a/src/blenderbim/blenderbim/core/tool/role_editor.py b/src/blenderbim/blenderbim/core/tool/role_editor.py new file mode 100644 index 0000000000..0107917948 --- /dev/null +++ b/src/blenderbim/blenderbim/core/tool/role_editor.py @@ -0,0 +1,46 @@ +# 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 abc + + +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 diff --git a/src/blenderbim/blenderbim/tool/__init__.py b/src/blenderbim/blenderbim/tool/__init__.py new file mode 100644 index 0000000000..e330be0cdf --- /dev/null +++ b/src/blenderbim/blenderbim/tool/__init__.py @@ -0,0 +1,23 @@ +# 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 . + +from blenderbim.tool.ifc import Ifc +from blenderbim.tool.blender import Blender +from blenderbim.tool.person_editor import PersonEditor +from blenderbim.tool.role_editor import RoleEditor +from blenderbim.tool.address_editor import AddressEditor diff --git a/src/blenderbim/blenderbim/tool/address_editor.py b/src/blenderbim/blenderbim/tool/address_editor.py new file mode 100644 index 0000000000..0547666251 --- /dev/null +++ b/src/blenderbim/blenderbim/tool/address_editor.py @@ -0,0 +1,108 @@ +# 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/bim/tool.py b/src/blenderbim/blenderbim/tool/blender.py similarity index 81% rename from src/blenderbim/blenderbim/bim/tool.py rename to src/blenderbim/blenderbim/tool/blender.py index 477f5b4a78..7b3bcffddf 100644 --- a/src/blenderbim/blenderbim/bim/tool.py +++ b/src/blenderbim/blenderbim/tool/blender.py @@ -16,17 +16,11 @@ # 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 from blenderbim.bim.ifc import IfcStore -class Ifc: - @classmethod - def run(cls, command, **kwargs): - return ifcopenshell.api.run(command, IfcStore.get_file(), **kwargs) - - class Blender: - @classmethod - def get_ifc(cls): - return "ifc" + pass diff --git a/src/blenderbim/blenderbim/tool/ifc.py b/src/blenderbim/blenderbim/tool/ifc.py new file mode 100644 index 0000000000..3386238efa --- /dev/null +++ b/src/blenderbim/blenderbim/tool/ifc.py @@ -0,0 +1,40 @@ +# 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 +from blenderbim.bim.ifc import IfcStore + + +class Ifc: + @classmethod + def run(cls, command, **kwargs): + return ifcopenshell.api.run(command, IfcStore.get_file(), **kwargs) + + @classmethod + def set(cls, ifc): + IfcStore.file = ifc + + @classmethod + def get(cls): + return IfcStore.get_file() + + @classmethod + def get_schema(cls): + return IfcStore.get_file().schema diff --git a/src/blenderbim/blenderbim/tool/person_editor.py b/src/blenderbim/blenderbim/tool/person_editor.py new file mode 100644 index 0000000000..31407a779e --- /dev/null +++ b/src/blenderbim/blenderbim/tool/person_editor.py @@ -0,0 +1,86 @@ +# 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.person_editor.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/role_editor.py b/src/blenderbim/blenderbim/tool/role_editor.py new file mode 100644 index 0000000000..4b214bff1e --- /dev/null +++ b/src/blenderbim/blenderbim/tool/role_editor.py @@ -0,0 +1,47 @@ +# 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.tool as tool +import blenderbim.bim.helper + + +class RoleEditor(blenderbim.core.tool.role_editor.RoleEditor): + @classmethod + def set_role(cls, role): + bpy.context.scene.BIMOwnerProperties.active_role_id = role.id() + + @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()) + + @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) diff --git a/src/blenderbim/test/bim/bootstrap.py b/src/blenderbim/test/bim/bootstrap.py index f2559ceac8..42425fe715 100644 --- a/src/blenderbim/test/bim/bootstrap.py +++ b/src/blenderbim/test/bim/bootstrap.py @@ -32,7 +32,7 @@ from mathutils import Vector webbrowser.open = lambda x: True -variables = {"cwd": os.getcwd()} +variables = {"cwd": os.getcwd(), "ifc": "IfcStore.get_file()"} class NewFile: @@ -45,6 +45,17 @@ class NewFile: bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True) +class NewIfc: + @pytest.fixture(autouse=True) + def setup(self): + IfcStore.purge() + bpy.ops.wm.read_homefile(app_template="") + while bpy.data.objects: + bpy.data.objects.remove(bpy.data.objects[0]) + bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True) + bpy.ops.bim.create_project() + + def scenario(function): def subfunction(self): run(function(self)) diff --git a/src/blenderbim/test/bim/module/owner/__init__.py b/src/blenderbim/test/bim/module/owner/__init__.py new file mode 100644 index 0000000000..fb33323db9 --- /dev/null +++ b/src/blenderbim/test/bim/module/owner/__init__.py @@ -0,0 +1,17 @@ +# 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 . diff --git a/src/blenderbim/test/bim/module/owner/test_operator.py b/src/blenderbim/test/bim/module/owner/test_operator.py new file mode 100644 index 0000000000..d75eab8854 --- /dev/null +++ b/src/blenderbim/test/bim/module/owner/test_operator.py @@ -0,0 +1,279 @@ +# 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 test.bim.bootstrap + + +class TestAddPerson(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_run(self): + return """ + Given an empty IFC project + When I press "bim.add_person" + Then nothing interesting happens + """ + + +class TestRemovePerson(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_run(self): + return """ + Given an empty IFC project + And I press "bim.add_person" + And the variable "person" is "{ifc}.by_type('IfcPerson')[0].id()" + When I press "bim.remove_person(person={person})" + Then nothing interesting happens + """ + + +class TestAddPersonAttribute(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_run(self): + return """ + Given an empty IFC project + And I press "bim.add_person" + When I press "bim.add_person_attribute(name='MiddleNames')" + And I press "bim.add_person_attribute(name='PrefixTitles')" + And I press "bim.add_person_attribute(name='SuffixTitles')" + Then nothing interesting happens + """ + + +class TestRemovePersonAttribute(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_run(self): + return """ + Given an empty IFC project + And I press "bim.add_person" + When I press "bim.add_person_attribute(name='MiddleNames')" + And I press "bim.remove_person_attribute(name='MiddleNames', id=0)" + And I press "bim.add_person_attribute(name='PrefixTitles')" + And I press "bim.remove_person_attribute(name='PrefixTitles', id=0)" + And I press "bim.add_person_attribute(name='SuffixTitles')" + And I press "bim.remove_person_attribute(name='SuffixTitles', id=0)" + Then nothing interesting happens + """ + + +class TestEnableEditingPerson(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_run(self): + return """ + Given an empty IFC project + And I press "bim.add_person" + And the variable "person" is "{ifc}.by_type('IfcPerson')[0].id()" + When I press "bim.enable_editing_person(person={person})" + Then "scene.BIMOwnerProperties.active_person_id" is "{person}" + """ + + +class TestDisableEditingPerson(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_run(self): + return """ + Given an empty IFC project + And I press "bim.add_person" + And the variable "person" is "{ifc}.by_type('IfcPerson')[0].id()" + And I press "bim.enable_editing_person(person={person})" + When I press "bim.disable_editing_person" + Then "scene.BIMOwnerProperties.active_person_id" is "0" + """ + + +class TestEditPerson(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_run(self): + return """ + Given an empty IFC project + And I press "bim.add_person" + And the variable "person" is "{ifc}.by_type('IfcPerson')[0].id()" + And I press "bim.enable_editing_person(person={person})" + When I press "bim.edit_person" + Then "scene.BIMOwnerProperties.active_person_id" is "0" + """ + + +class TestAddRole(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_run(self): + return """ + Given an empty IFC project + And I press "bim.add_person" + And the variable "person" is "{ifc}.by_type('IfcPerson')[0].id()" + When I press "bim.add_role(parent={person})" + Then nothing interesting happens + """ + + +class TestRemoveRole(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_run(self): + return """ + Given an empty IFC project + And I press "bim.add_person" + And the variable "person" is "{ifc}.by_type('IfcPerson')[0].id()" + And I press "bim.add_role(parent={person})" + And the variable "role" is "{ifc}.by_type('IfcActorRole')[0].id()" + When I press "bim.remove_role(role={role})" + Then nothing interesting happens + """ + + +class TestEnableEditingRole(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_run(self): + return """ + Given an empty IFC project + And I press "bim.add_person" + And the variable "person" is "{ifc}.by_type('IfcPerson')[0].id()" + And I press "bim.add_role(parent={person})" + And the variable "role" is "{ifc}.by_type('IfcActorRole')[0].id()" + When I press "bim.enable_editing_role(role={role})" + Then nothing interesting happens + """ + + +class TestDisableEditingRole(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_run(self): + return """ + Given an empty IFC project + And I press "bim.add_person" + And the variable "person" is "{ifc}.by_type('IfcPerson')[0].id()" + And I press "bim.add_role(parent={person})" + And the variable "role" is "{ifc}.by_type('IfcActorRole')[0].id()" + And I press "bim.enable_editing_role(role={role})" + When I press "bim.disable_editing_role()" + Then nothing interesting happens + """ + + +class TestEditRole(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_run(self): + return """ + Given an empty IFC project + And I press "bim.add_person" + And the variable "person" is "{ifc}.by_type('IfcPerson')[0].id()" + And I press "bim.add_role(parent={person})" + And the variable "role" is "{ifc}.by_type('IfcActorRole')[0].id()" + And I press "bim.enable_editing_role(role={role})" + When I press "bim.edit_role()" + Then nothing interesting happens + """ + + +class TestAddAddress(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_run(self): + return """ + Given an empty IFC project + And I press "bim.add_person" + And the variable "person" is "{ifc}.by_type('IfcPerson')[0].id()" + When I press "bim.add_address(parent={person}, ifc_class='IfcPostalAddress')" + Then nothing interesting happens + """ + + +class TestAddAddressAttribute(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_run(self): + return """ + Given an empty IFC project + And I press "bim.add_person" + And the variable "person" is "{ifc}.by_type('IfcPerson')[0].id()" + And I press "bim.add_address(parent={person}, ifc_class='IfcPostalAddress')" + And the variable "address" is "{ifc}.by_type('IfcPostalAddress')[0].id()" + And I press "bim.enable_editing_address(address={address})" + When I press "bim.add_address_attribute(name='AddressLines')" + Then nothing interesting happens + """ + + +class TestRemoveAddressAttribute(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_run(self): + return """ + Given an empty IFC project + And I press "bim.add_person" + And the variable "person" is "{ifc}.by_type('IfcPerson')[0].id()" + And I press "bim.add_address(parent={person}, ifc_class='IfcPostalAddress')" + And the variable "address" is "{ifc}.by_type('IfcPostalAddress')[0].id()" + And I press "bim.enable_editing_address(address={address})" + And I press "bim.add_address_attribute(name='AddressLines')" + When I press "bim.remove_address_attribute(name='AddressLines', id=0)" + Then nothing interesting happens + """ + + +class TestRemoveAddress(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_run(self): + return """ + Given an empty IFC project + And I press "bim.add_person" + And the variable "person" is "{ifc}.by_type('IfcPerson')[0].id()" + And I press "bim.add_address(parent={person}, ifc_class='IfcPostalAddress')" + And the variable "address" is "{ifc}.by_type('IfcPostalAddress')[0].id()" + When I press "bim.remove_address(address={address})" + Then nothing interesting happens + """ + + +class TestEnableEditingAddress(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_run(self): + return """ + Given an empty IFC project + And I press "bim.add_person" + And the variable "person" is "{ifc}.by_type('IfcPerson')[0].id()" + And I press "bim.add_address(parent={person}, ifc_class='IfcPostalAddress')" + And the variable "address" is "{ifc}.by_type('IfcPostalAddress')[0].id()" + When I press "bim.enable_editing_address(address={address})" + Then nothing interesting happens + """ + + +class TestDisableEditingAddress(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_run(self): + return """ + Given an empty IFC project + And I press "bim.add_person" + And the variable "person" is "{ifc}.by_type('IfcPerson')[0].id()" + And I press "bim.add_address(parent={person}, ifc_class='IfcPostalAddress')" + And the variable "address" is "{ifc}.by_type('IfcPostalAddress')[0].id()" + And I press "bim.enable_editing_address(address={address})" + When I press "bim.disable_editing_address()" + Then nothing interesting happens + """ + + +class TestEditAddress(test.bim.bootstrap.NewFile): + @test.bim.bootstrap.scenario + def test_run(self): + return """ + Given an empty IFC project + And I press "bim.add_person" + And the variable "person" is "{ifc}.by_type('IfcPerson')[0].id()" + And I press "bim.add_address(parent={person}, ifc_class='IfcPostalAddress')" + And the variable "address" is "{ifc}.by_type('IfcPostalAddress')[0].id()" + And I press "bim.enable_editing_address(address={address})" + When I press "bim.edit_address()" + Then nothing interesting happens + """ diff --git a/src/blenderbim/test/core/bootstrap.py b/src/blenderbim/test/core/bootstrap.py index 839dd2ee46..353e79df41 100644 --- a/src/blenderbim/test/core/bootstrap.py +++ b/src/blenderbim/test/core/bootstrap.py @@ -35,25 +35,25 @@ def blender(): prophet.verify() -def subject(sus): - def decorate(cls): - cls.sus = sus - return cls - - return decorate +@pytest.fixture +def person_editor(): + prophet = Prophecy(blenderbim.core.tool.PersonEditor) + yield prophet + prophet.verify() -class Spec: - @pytest.fixture(autouse=True) - def setup(self): - self.subject = None +@pytest.fixture +def role_editor(): + prophet = Prophecy(blenderbim.core.tool.RoleEditor) + yield prophet + prophet.verify() - def construct_with(self, *args, **kwargs): - self.subject = self.sus(*args, **kwargs) - return self.subject - def predict(self, cls): - return Prophecy(cls) +@pytest.fixture +def address_editor(): + prophet = Prophecy(blenderbim.core.tool.AddressEditor) + yield prophet + prophet.verify() class Prophecy: @@ -66,10 +66,12 @@ class Prophecy: def __getattr__(self, attr): if not hasattr(self.subject, attr): - raise AttributeError(f"Prophecy has no attribute {attr}") + raise AttributeError(f"Prophecy {self.subject} has no attribute {attr}") def decorate(*args, **kwargs): call = {"name": attr, "args": args, "kwargs": kwargs} + # Ensure that signature is valid + getattr(self.subject, attr)(*args, **kwargs) try: key = json.dumps(call, sort_keys=True) self.calls.append(call) @@ -81,23 +83,25 @@ class Prophecy: return decorate - def should(self): + def should_be_called(self, number=None): self.should_call = self.calls.pop() - return self - - def be_called(self, number=None): self.predictions.append({"type": "SHOULD_BE_CALLED", "number": number, "call": self.should_call}) return self - def return_with(self, value): + def will_return(self, value): key = json.dumps(self.should_call, sort_keys=True) self.return_values[key] = value return self def verify(self): + predicted_calls = [] for prediction in self.predictions: + predicted_calls.append(prediction["call"]) if prediction["type"] == "SHOULD_BE_CALLED": self.verify_should_be_called(prediction) + for call in self.calls: + if call not in predicted_calls: + raise Exception(f"Unpredicted call: {call}") def verify_should_be_called(self, prediction): if prediction["number"]: @@ -106,4 +110,4 @@ class Prophecy: raise Exception(f"Called {count}: {prediction}") else: if prediction["call"] not in self.calls: - raise Exception("Not called", prediction) + raise Exception(f"{self.subject} was not called with {prediction['call']['name']}: {prediction}") diff --git a/src/blenderbim/test/core/test_owner.py b/src/blenderbim/test/core/test_owner.py new file mode 100644 index 0000000000..85cf485f9b --- /dev/null +++ b/src/blenderbim/test/core/test_owner.py @@ -0,0 +1,149 @@ +# 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.owner as subject +from test.core.bootstrap import ifc, blender, person_editor, role_editor, address_editor + + +class TestAddPerson: + def test_run(self, ifc): + ifc.run("owner.add_person").should_be_called().will_return("person") + assert subject.add_person(ifc) == "person" + + +class TestRemovePerson: + def test_run(self, ifc): + ifc.run("owner.remove_person", person="person").should_be_called() + subject.remove_person(ifc, person="person") + + +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") + + +class TestDisableEditingPerson: + def test_run(self, person_editor): + person_editor.clear_person().should_be_called() + subject.disable_editing_person(person_editor) + + +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") + 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) + + +class TestAddPersonAttribute: + def test_run(self, person_editor): + person_editor.add_attribute("name").should_be_called() + subject.add_person_attribute(person_editor, 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") + + +class TestAddRole: + def test_run(self, ifc): + ifc.run("owner.add_role", assigned_object="parent").should_be_called().will_return("role") + assert subject.add_role(ifc, parent="parent") == "role" + + +class TestRemoveRole: + def test_run(self, ifc): + ifc.run("owner.remove_role", role="role").should_be_called() + subject.remove_role(ifc, role="role") + + +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") + + +class TestDisableEditingRole: + def test_run(self, role_editor): + role_editor.clear_role().should_be_called() + subject.disable_editing_role(role_editor) + + +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") + 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) + + +class TestAddAddress: + def test_run(self, ifc): + ifc.run( + "owner.add_address", assigned_object="parent", ifc_class="IfcPostalAddress" + ).should_be_called().will_return("address") + assert subject.add_address(ifc, parent="parent", ifc_class="IfcPostalAddress") == "address" + + +class TestRemoveAddress: + def test_run(self, ifc): + ifc.run("owner.remove_address", address="address").should_be_called() + subject.remove_address(ifc, address="address") + + +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") + + +class TestDisableEditingAddress: + def test_run(self, address_editor): + address_editor.clear_address().should_be_called() + subject.disable_editing_address(address_editor) + + +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") + 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) + + +class TestAddAddressAttribute: + def test_run(self, address_editor): + address_editor.add_attribute("name").should_be_called() + subject.add_address_attribute(address_editor, 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") diff --git a/src/blenderbim/test/tool/__init__.py b/src/blenderbim/test/tool/__init__.py new file mode 100644 index 0000000000..fb33323db9 --- /dev/null +++ b/src/blenderbim/test/tool/__init__.py @@ -0,0 +1,17 @@ +# 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 . diff --git a/src/blenderbim/test/tool/test_address_editor.py b/src/blenderbim/test/tool/test_address_editor.py new file mode 100644 index 0000000000..c9350f13f3 --- /dev/null +++ b/src/blenderbim/test/tool/test_address_editor.py @@ -0,0 +1,197 @@ +# 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_blender.py b/src/blenderbim/test/tool/test_blender.py new file mode 100644 index 0000000000..fb33323db9 --- /dev/null +++ b/src/blenderbim/test/tool/test_blender.py @@ -0,0 +1,17 @@ +# 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 . diff --git a/src/blenderbim/test/tool/test_ifc.py b/src/blenderbim/test/tool/test_ifc.py new file mode 100644 index 0000000000..791868a963 --- /dev/null +++ b/src/blenderbim/test/tool/test_ifc.py @@ -0,0 +1,58 @@ +# 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 +from blenderbim.tool import Ifc as subject + + +class TestSet(test.bim.bootstrap.NewFile): + def test_setting_an_ifc_data(self): + ifc = ifcopenshell.file() + subject().set(ifc) + assert subject().get() == ifc + + +class TestGet(test.bim.bootstrap.NewFile): + def test_getting_an_ifc_dataset_from_a_ifc_spf_filepath(self): + assert subject().get() is None + bpy.context.scene.BIMProperties.ifc_file = "test/files/basic.ifc" + result = subject().get() + assert isinstance(result, ifcopenshell.file) + + def test_getting_the_active_ifc_dataset_regardless_of_ifc_path(self): + bpy.context.scene.BIMProperties.ifc_file = "test/files/basic.ifc" + ifc = ifcopenshell.file() + subject().set(ifc) + assert subject().get() == ifc + + +class TestRun(test.bim.bootstrap.NewFile): + def test_running_a_command_on_the_active_ifc_dataset(self): + ifc = ifcopenshell.file() + subject().set(ifc) + wall = subject().run("root.create_entity", ifc_class="IfcWall") + assert subject().get().by_type("IfcWall")[0] == wall + + +class TestGetSchema(test.bim.bootstrap.NewFile): + def test_getting_the_schema_version_identifier(self): + ifc = ifcopenshell.file(schema="IFC4") + subject().set(ifc) + assert subject().get_schema() == "IFC4" diff --git a/src/blenderbim/test/tool/test_person_editor.py b/src/blenderbim/test/tool/test_person_editor.py new file mode 100644 index 0000000000..50a6a78600 --- /dev/null +++ b/src/blenderbim/test/tool/test_person_editor.py @@ -0,0 +1,152 @@ +# 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.person_editor.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 new file mode 100644 index 0000000000..67d9fd014f --- /dev/null +++ b/src/blenderbim/test/tool/test_role_editor.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 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.role_editor.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", + }