Add support for null attributes for people, roles, and addresses. New prototoype testable architecture. See #1711.

This commit is contained in:
Dion Moult
2021-09-29 14:14:10 +10:00
parent 6fb4714317
commit 086d8749ec
31 changed files with 1975 additions and 361 deletions
+5
View File
@@ -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 .
@@ -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,
@@ -0,0 +1,129 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
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
@@ -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):
@@ -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")
@@ -17,10 +17,12 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
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):
@@ -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
+100
View File
@@ -0,0 +1,100 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
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)
@@ -0,0 +1,23 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
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
@@ -0,0 +1,56 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
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
@@ -0,0 +1,23 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
import abc
class Blender(abc.ABC):
pass
@@ -16,15 +16,11 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
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
@@ -0,0 +1,56 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
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
@@ -0,0 +1,46 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
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
@@ -0,0 +1,23 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
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
@@ -0,0 +1,108 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
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)
@@ -16,17 +16,11 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
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
+40
View File
@@ -0,0 +1,40 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
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
@@ -0,0 +1,86 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
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)
@@ -0,0 +1,47 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
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)
+12 -1
View File
@@ -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))
@@ -0,0 +1,17 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
@@ -0,0 +1,279 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
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
"""
+26 -22
View File
@@ -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}")
+149
View File
@@ -0,0 +1,149 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
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")
+17
View File
@@ -0,0 +1,17 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
@@ -0,0 +1,197 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
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
+17
View File
@@ -0,0 +1,17 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
+58
View File
@@ -0,0 +1,58 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
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"
@@ -0,0 +1,152 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
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
@@ -0,0 +1,98 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
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",
}