You can now add and remove users for ownership history

This commit is contained in:
Dion Moult
2021-10-01 17:32:58 +10:00
parent fe1e30702a
commit 4697e063af
23 changed files with 336 additions and 27 deletions
+3 -10
View File
@@ -20,6 +20,8 @@ import bpy
import json
import addon_utils
import ifcopenshell.api.owner.settings
import blenderbim.tool as tool
import blenderbim.core.owner as core_owner
from blenderbim.bim.module.drawing.prop import RasterStyleProperty
from bpy.app.handlers import persistent
from blenderbim.bim.ifc import IfcStore
@@ -228,16 +230,7 @@ def setDefaultProperties(scene):
bpy.msgbus.subscribe_rna(
key=active_object_key, owner=global_subscription_owner, args=(), notify=active_object_callback
)
ifcopenshell.api.owner.settings.get_person = (
lambda ifc: ifc.by_id(int(bpy.context.scene.BIMOwnerProperties.user_person))
if get_user_person(None, None) and bpy.context.scene.BIMOwnerProperties.user_person
else None
)
ifcopenshell.api.owner.settings.get_organisation = (
lambda ifc: ifc.by_id(int(bpy.context.scene.BIMOwnerProperties.user_organisation))
if get_user_organisation(None, None) and bpy.context.scene.BIMOwnerProperties.user_organisation
else None
)
ifcopenshell.api.owner.settings.get_user = lambda ifc: core_owner.get_user(tool.Owner)
ifcopenshell.api.owner.settings.get_application = get_application
if len(bpy.context.scene.DocProperties.drawing_styles) == 0:
drawing_style = bpy.context.scene.DocProperties.drawing_styles.add()
@@ -24,6 +24,7 @@ classes = (
operator.AddAddressAttribute,
operator.AddOrganisation,
operator.AddPerson,
operator.AddPersonAndOrganisation,
operator.AddPersonAttribute,
operator.AddRole,
operator.DisableEditingAddress,
@@ -42,8 +43,10 @@ classes = (
operator.RemoveAddressAttribute,
operator.RemoveOrganisation,
operator.RemovePerson,
operator.RemovePersonAndOrganisation,
operator.RemovePersonAttribute,
operator.RemoveRole,
operator.SetUser,
prop.BIMOwnerProperties,
ui.BIM_PT_people,
ui.BIM_PT_organisations,
@@ -164,7 +164,12 @@ class OwnerData:
@classmethod
def load(cls):
cls.data = {"user_person": cls.get_user_person(), "user_organisation": cls.get_user_organisation()}
cls.data = {
"user_person": cls.get_user_person(),
"user_organisation": cls.get_user_organisation(),
"can_add_user": cls.can_add_user(),
"users": cls.get_users(),
}
cls.is_loaded = True
@classmethod
@@ -174,3 +179,18 @@ class OwnerData:
@classmethod
def get_user_organisation(cls):
return [(str(p.id()), p[0] or "Unnamed", "") for p in tool.Ifc.get().by_type("IfcOrganization")]
@classmethod
def can_add_user(cls):
return tool.Ifc.get().by_type("IfcPerson") and tool.Ifc.get().by_type("IfcOrganization")
@classmethod
def get_users(cls):
results = []
for user in tool.Ifc.get().by_type("IfcPersonAndOrganization"):
results.append({
"id": user.id(),
"label": "{} ({})".format(user.ThePerson[0] or "Unnamed", user.TheOrganization[0] or "Unnamed"),
"is_active": bpy.context.scene.BIMOwnerProperties.active_user_id == user.id(),
})
return results
@@ -264,3 +264,38 @@ class RemoveOrganisation(bpy.types.Operator, Operator):
def _execute(self, context):
core.remove_organisation(tool.Ifc, tool.Ifc.get().by_id(self.organisation))
class AddPersonAndOrganisation(bpy.types.Operator, Operator):
bl_idname = "bim.add_person_and_organisation"
bl_label = "Add Person And Organisation"
bl_options = {"REGISTER", "UNDO"}
person: bpy.props.IntProperty()
organisation: bpy.props.IntProperty()
def _execute(self, context):
core.add_person_and_organisation(
tool.Ifc, person=tool.Ifc.get().by_id(self.person), organisation=tool.Ifc.get().by_id(self.organisation)
)
class RemovePersonAndOrganisation(bpy.types.Operator, Operator):
bl_idname = "bim.remove_person_and_organisation"
bl_label = "Remove Person And Organisation"
bl_options = {"REGISTER", "UNDO"}
person_and_organisation: bpy.props.IntProperty()
def _execute(self, context):
core.remove_person_and_organisation(
tool.Ifc, tool.Owner, person_and_organisation=tool.Ifc.get().by_id(self.person_and_organisation)
)
class SetUser(bpy.types.Operator, Operator):
bl_idname = "bim.set_user"
bl_label = "set_user"
bl_options = {"REGISTER", "UNDO"}
user: bpy.props.IntProperty()
def _execute(self, context):
core.set_user(tool.Owner, user=tool.Ifc.get().by_id(self.user))
@@ -63,3 +63,4 @@ class BIMOwnerProperties(PropertyGroup):
messaging_ids: CollectionProperty(type=StrProperty, name="IMs")
user_person: EnumProperty(items=get_user_person, name="Person")
user_organisation: EnumProperty(items=get_user_organisation, name="Organisation")
active_user_id: IntProperty(name="Active User Id")
@@ -207,3 +207,19 @@ class BIM_PT_owner(bpy.types.Panel):
else:
row = self.layout.row()
row.prop(props, "user_organisation")
if OwnerData.data["can_add_user"]:
row = self.layout.row()
op = row.operator("bim.add_person_and_organisation", icon="ADD")
op.person = int(props.user_person)
op.organisation = int(props.user_organisation)
for user in OwnerData.data["users"]:
row = self.layout.row(align=True)
if user["is_active"]:
row.label(text=user["label"], icon="USER")
else:
row.label(text=user["label"])
row.operator("bim.set_user", icon="KEYFRAME_HLT", text="").user = user["id"]
op = row.operator("bim.remove_person_and_organisation", icon="X", text="")
op.person_and_organisation = user["id"]
@@ -27,7 +27,8 @@ import ifcopenshell.util.selector
import ifcopenshell.util.representation
import blenderbim.bim.handler
import blenderbim.tool as tool
import blenderbim.core.context as context_core
import blenderbim.core.context as core_context
import blenderbim.core.owner as core_owner
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim import import_ifc
@@ -57,8 +58,10 @@ class CreateProject(bpy.types.Operator):
)
self.file = IfcStore.get_file()
bpy.ops.bim.add_person()
bpy.ops.bim.add_organisation()
person = core_owner.add_person(tool.Ifc)
organisation = core_owner.add_organisation(tool.Ifc)
user = core_owner.add_person_and_organisation(tool.Ifc, person=person, organisation=organisation)
core_owner.set_user(tool.Owner, user=user)
project = bpy.data.objects.new(self.get_name("IfcProject", "My Project"), None)
site = bpy.data.objects.new(self.get_name("IfcSite", "My Site"), None)
@@ -68,18 +71,17 @@ class CreateProject(bpy.types.Operator):
bpy.ops.bim.assign_class(obj=project.name, ifc_class="IfcProject")
bpy.ops.bim.assign_unit()
# TODO: refactor
model = context_core.add_context(
model = core_context.add_context(
tool.Ifc, context_type="Model", context_identifier="", target_view="", parent=0
)
context_core.add_context(
core_context.add_context(
tool.Ifc, context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model
)
context_core.add_context(
core_context.add_context(
tool.Ifc, context_type="Model", context_identifier="Box", target_view="MODEL_VIEW", parent=model
)
plan = context_core.add_context(tool.Ifc, context_type="Plan", context_identifier="", target_view="", parent=0)
context_core.add_context(
plan = core_context.add_context(tool.Ifc, context_type="Plan", context_identifier="", target_view="", parent=0)
core_context.add_context(
tool.Ifc, context_type="Plan", context_identifier="Annotation", target_view="PLAN_VIEW", parent=plan
)
+18
View File
@@ -121,3 +121,21 @@ def edit_organisation(ifc, organisation_editor):
organisation = organisation_editor.get_organisation()
ifc.run("owner.edit_organisation", organisation=organisation, attributes=organisation_editor.export_attributes())
organisation_editor.clear_organisation()
def add_person_and_organisation(ifc, person=None, organisation=None):
return ifc.run("owner.add_person_and_organisation", person=person, organisation=organisation)
def remove_person_and_organisation(ifc, owner, person_and_organisation):
if owner.get_user() == person_and_organisation:
owner.clear_user()
ifc.run("owner.remove_person_and_organisation", person_and_organisation=person_and_organisation)
def set_user(owner, user=None):
owner.set_user(user)
def get_user(owner):
return owner.get_user()
@@ -23,3 +23,4 @@ from blenderbim.core.tool.role_editor import RoleEditor
from blenderbim.core.tool.address_editor import AddressEditor
from blenderbim.core.tool.organisation_editor import OrganisationEditor
from blenderbim.core.tool.context_editor import ContextEditor
from blenderbim.core.tool.owner import Owner
@@ -0,0 +1,18 @@
import abc
class Owner(abc.ABC):
@classmethod
@abc.abstractmethod
def set_user(cls, user):
pass
@classmethod
@abc.abstractmethod
def get_user(cls):
pass
@classmethod
@abc.abstractmethod
def clear_user(cls):
pass
@@ -23,3 +23,4 @@ from blenderbim.tool.role_editor import RoleEditor
from blenderbim.tool.address_editor import AddressEditor
from blenderbim.tool.organisation_editor import OrganisationEditor
from blenderbim.tool.context_editor import ContextEditor
from blenderbim.tool.owner import Owner
+17
View File
@@ -0,0 +1,17 @@
import bpy
import blenderbim.core.tool
import blenderbim.tool as tool
class Owner(blenderbim.core.tool.owner.Owner):
@classmethod
def set_user(cls, user):
bpy.context.scene.BIMOwnerProperties.active_user_id = user.id()
@classmethod
def get_user(cls):
if bpy.context.scene.BIMOwnerProperties.active_user_id:
return tool.Ifc.get().by_id(bpy.context.scene.BIMOwnerProperties.active_user_id)
@classmethod
def clear_user(cls):
bpy.context.scene.BIMOwnerProperties.active_user_id = 0
+2
View File
@@ -44,6 +44,7 @@ class NewFile:
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)
blenderbim.bim.handler.setDefaultProperties(None)
class NewIfc:
@@ -54,6 +55,7 @@ class NewIfc:
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)
blenderbim.bim.handler.setDefaultProperties(None)
bpy.ops.bim.create_project()
+34 -2
View File
@@ -10,7 +10,7 @@ Scenario: Add person
Scenario: Remove person
Given an empty IFC project
When I press "bim.add_person"
And the variable "person" is "{ifc}.by_type('IfcPerson')[0].id()"
And the variable "person" is "{ifc}.by_type('IfcPerson')[-1].id()"
And I press "bim.remove_person(person={person})"
Then nothing happens
@@ -198,6 +198,38 @@ Scenario: Edit organisation
Scenario: Remove organisation
Given an empty IFC project
When I press "bim.add_organisation"
And the variable "organisation" is "{ifc}.by_type('IfcOrganization')[0].id()"
And the variable "organisation" is "{ifc}.by_type('IfcOrganization')[-1].id()"
And I press "bim.remove_organisation(organisation={organisation})"
Then nothing happens
Scenario: Add person and organisation
Given an empty IFC project
When I press "bim.add_person"
And I press "bim.add_organisation"
And the variable "person" is "{ifc}.by_type('IfcPerson')[0].id()"
And the variable "organisation" is "{ifc}.by_type('IfcOrganization')[0].id()"
And I press "bim.add_person_and_organisation(person={person}, organisation={organisation})"
Then nothing happens
Scenario: Remove person and organisation
Given an empty IFC project
When I press "bim.add_person"
And I press "bim.add_organisation"
And the variable "person" is "{ifc}.by_type('IfcPerson')[-1].id()"
And the variable "organisation" is "{ifc}.by_type('IfcOrganization')[-1].id()"
And I press "bim.add_person_and_organisation(person={person}, organisation={organisation})"
# Note: these are set to -1 temporarily to prevent crashing due to bug #1747
And the variable "pno" is "{ifc}.by_type('IfcPersonAndOrganization')[-1].id()"
And I press "bim.remove_person_and_organisation(person_and_organisation={pno})"
Then nothing happens
Scenario: Set user
Given an empty IFC project
When I press "bim.add_person"
And I press "bim.add_organisation"
And the variable "person" is "{ifc}.by_type('IfcPerson')[0].id()"
And the variable "organisation" is "{ifc}.by_type('IfcOrganization')[0].id()"
And I press "bim.add_person_and_organisation(person={person}, organisation={organisation})"
And the variable "user" is "{ifc}.by_type('IfcPersonAndOrganization')[0].id()"
And I press "bim.set_user(user={user})"
Then nothing happens
+7
View File
@@ -70,6 +70,13 @@ def context_editor():
prophet.verify()
@pytest.fixture
def owner():
prophet = Prophecy(blenderbim.core.tool.Owner)
yield prophet
prophet.verify()
class Prophecy:
def __init__(self, cls):
self.subject = cls
+39 -1
View File
@@ -18,7 +18,7 @@
import blenderbim.core.owner as subject
from test.core.bootstrap import ifc, blender, person_editor, role_editor, address_editor, organisation_editor
from test.core.bootstrap import ifc, blender, person_editor, role_editor, address_editor, organisation_editor, owner
class TestAddPerson:
@@ -181,3 +181,41 @@ class TestEditOrganisation:
ifc.run("owner.edit_organisation", organisation="organisation", attributes="attributes").should_be_called()
organisation_editor.clear_organisation().should_be_called()
subject.edit_organisation(ifc, organisation_editor)
class TestAddPersonAndOrganisation:
def test_run(self, ifc):
ifc.run(
"owner.add_person_and_organisation", person="person", organisation="organisation"
).should_be_called().will_return("person_and_organisation")
assert (
subject.add_person_and_organisation(ifc, person="person", organisation="organisation")
== "person_and_organisation"
)
class TestRemovePersonAndOrganisation:
def test_run(self, ifc, owner):
owner.get_user().should_be_called().will_return("user")
ifc.run(
"owner.remove_person_and_organisation", person_and_organisation="person_and_organisation"
).should_be_called()
subject.remove_person_and_organisation(ifc, owner, person_and_organisation="person_and_organisation")
def test_clearing_the_active_user_if_you_remove_it(self, ifc, owner):
owner.get_user().should_be_called().will_return("user")
owner.clear_user().should_be_called()
ifc.run("owner.remove_person_and_organisation", person_and_organisation="user").should_be_called()
subject.remove_person_and_organisation(ifc, owner, person_and_organisation="user")
class TestSetUser:
def test_run(self, owner):
owner.set_user("person_and_organisation").should_be_called()
subject.set_user(owner, user="person_and_organisation")
class TestGetUser:
def test_run(self, owner):
owner.get_user().should_be_called().will_return("person_and_organisation")
assert subject.get_user(owner) == "person_and_organisation"
+35
View File
@@ -0,0 +1,35 @@
import bpy
import ifcopenshell
import test.bim.bootstrap
import blenderbim.core.tool
import blenderbim.tool as tool
from blenderbim.tool.owner import Owner as subject
class TestImplementsTool(test.bim.bootstrap.NewFile):
def test_run(self):
assert isinstance(subject(), blenderbim.core.tool.owner.Owner)
class TestSetUser(test.bim.bootstrap.NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
user = ifc.createIfcPersonAndOrganization()
subject.set_user(user)
assert bpy.context.scene.BIMOwnerProperties.active_user_id == user.id()
class TestGetUser(test.bim.bootstrap.NewFile):
def test_run(self):
assert subject.get_user() is None
TestSetUser().test_run()
user = tool.Ifc.get().by_type("IfcPersonAndOrganization")[0]
assert subject.get_user() == user
class TestClearUser(test.bim.bootstrap.NewFile):
def test_run(self):
TestSetUser().test_run()
subject.clear_user()
assert bpy.context.scene.BIMOwnerProperties.active_user_id == 0
@@ -1,9 +1,9 @@
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"actor": None}
self.settings = {"person": None, "organisation": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
self.file.remove(self.settings["actor"])
return self.file.createIfcPersonAndOrganization(self.settings["person"], self.settings["organisation"])
@@ -27,7 +27,7 @@ class Usecase:
elif inverse.is_a("IfcPersonAndOrganization"):
ifcopenshell.api.run("owner.remove_person_and_organisation", self.file, person_and_organisation=inverse)
elif inverse.is_a("IfcActor"):
ifcopenshell.api.run("owner.remove_actor", self.file, actor=inverse)
ifcopenshell.api.run("root.remove_product", self.file, product=inverse)
elif inverse.is_a("IfcResourceLevelRelationship") and not inverse.is_a("IfcOrganizationRelationship"):
if inverse.RelatedResourceObjects == (self.settings["organisation"],):
self.file.remove(inverse)
@@ -28,7 +28,7 @@ class Usecase:
elif inverse.is_a("IfcPersonAndOrganization"):
ifcopenshell.api.run("owner.remove_person_and_organisation", self.file, person_and_organisation=inverse)
elif inverse.is_a("IfcActor"):
ifcopenshell.api.run("owner.remove_actor", self.file, actor=inverse)
ifcopenshell.api.run("root.remove_product", self.file, product=inverse)
elif inverse.is_a("IfcResourceLevelRelationship"):
if inverse.RelatedResourceObjects == (self.settings["person"],):
self.file.remove(inverse)
@@ -1,3 +1,6 @@
import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
@@ -6,4 +9,15 @@ class Usecase:
self.settings[key] = value
def execute(self):
for inverse in self.file.get_inverse(self.settings["person_and_organisation"]):
if inverse.is_a("IfcDocumentInformation"):
if inverse.Editors == (self.settings["person_and_organisation"],):
inverse.Editors = None
elif inverse.is_a("IfcActor"):
ifcopenshell.api.run("root.remove_product", self.file, product=inverse)
elif inverse.is_a("IfcResourceLevelRelationship"):
if inverse.RelatedResourceObjects == (self.settings["person_and_organisation"],):
self.file.remove(inverse)
elif inverse.is_a("IfcOwnerHistory"):
self.file.remove(inverse)
self.file.remove(self.settings["person_and_organisation"])
@@ -0,0 +1,11 @@
import test.bootstrap
import ifcopenshell.api
class TestAddPersonAndOrganisation(test.bootstrap.IFC4):
def test_adding(self):
person = self.file.createIfcPerson()
organisation = self.file.createIfcOrganization()
person_and_organisation = ifcopenshell.api.run(
"owner.add_person_and_organisation", self.file, person=person, organisation=organisation
)
@@ -0,0 +1,45 @@
import test.bootstrap
import ifcopenshell.api
class TestRemovePersonAndOrganisation(test.bootstrap.IFC4):
def test_removing(self):
user = self.file.createIfcPersonAndOrganization()
ifcopenshell.api.run("owner.remove_person_and_organisation", self.file, person_and_organisation=user)
assert len(self.file.by_type("IfcPersonAndOrganization")) == 0
def test_deleting_actors(self):
user = self.file.createIfcPersonAndOrganization()
self.file.createIfcActor(GlobalId=ifcopenshell.guid.new(), TheActor=user)
ifcopenshell.api.run("owner.remove_person_and_organisation", self.file, person_and_organisation=user)
assert len(self.file.by_type("IfcActor")) == 0
def test_ensuring_document_information_should_not_be_left_in_an_invalid_set_cardinality(self):
user = self.file.createIfcPersonAndOrganization()
document_information = self.file.createIfcDocumentInformation(Editors=[user])
ifcopenshell.api.run("owner.remove_person_and_organisation", self.file, person_and_organisation=user)
assert document_information.Editors is None
def test_deleting_resource_approval_relationships(self):
user = self.file.createIfcPersonAndOrganization()
self.file.createIfcResourceApprovalRelationship(RelatedResourceObjects=[user])
ifcopenshell.api.run("owner.remove_person_and_organisation", self.file, person_and_organisation=user)
assert len(self.file.by_type("IfcResourceApprovalRelationship")) == 0
def test_deleting_resource_constraint_relationships(self):
user = self.file.createIfcPersonAndOrganization()
self.file.createIfcResourceConstraintRelationship(RelatedResourceObjects=[user])
ifcopenshell.api.run("owner.remove_person_and_organisation", self.file, person_and_organisation=user)
assert len(self.file.by_type("IfcResourceConstraintRelationship")) == 0
def test_deleting_external_reference_relationships(self):
user = self.file.createIfcPersonAndOrganization()
self.file.createIfcExternalReferenceRelationship(RelatedResourceObjects=[user])
ifcopenshell.api.run("owner.remove_person_and_organisation", self.file, person_and_organisation=user)
assert len(self.file.by_type("IfcExternalReferenceRelationship")) == 0
def test_deleting_owner_history(self):
user = self.file.createIfcPersonAndOrganization()
self.file.createIfcOwnerHistory(OwningUser=user)
ifcopenshell.api.run("owner.remove_person_and_organisation", self.file, person_and_organisation=user)
assert len(self.file.by_type("IfcOwnerHistory")) == 0