You can now manage actor assignments to products

This commit is contained in:
Dion Moult
2022-05-06 17:47:24 +10:00
parent b54caa8967
commit 5f1b5d0c63
12 changed files with 331 additions and 2 deletions
@@ -28,6 +28,7 @@ classes = (
operator.AddPersonAndOrganisation,
operator.AddPersonAttribute,
operator.AddRole,
operator.AssignActor,
operator.ClearUser,
operator.DisableEditingActor,
operator.DisableEditingAddress,
@@ -53,11 +54,13 @@ classes = (
operator.RemovePersonAttribute,
operator.RemoveRole,
operator.SetUser,
operator.UnassignActor,
prop.BIMOwnerProperties,
ui.BIM_PT_people,
ui.BIM_PT_organisations,
ui.BIM_PT_owner,
ui.BIM_PT_actor,
ui.BIM_PT_object_actor,
)
@@ -25,6 +25,7 @@ def refresh():
OrganisationsData.is_loaded = False
OwnerData.is_loaded = False
ActorData.is_loaded = False
ObjectActorData.is_loaded = False
class RolesAddressesData:
@@ -243,3 +244,46 @@ class ActorData:
{"id": actor.id(), "name": actor.Name or "Unnamed", "the_actor": the_actor, "is_editing": is_editing}
)
return actors
class ObjectActorData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.is_loaded = True
cls.data = {
"actor": cls.actor(),
"actors": cls.actors()
}
@classmethod
def actor(cls):
return [(str(p.id()), p.Name or "Unnamed", "") for p in tool.Ifc.get().by_type("IfcActor")]
@classmethod
def actors(cls):
results = []
element = tool.Ifc.get_entity(bpy.context.active_object)
if not element:
return results
for rel in getattr(element, "HasAssignments", []):
if rel.is_a("IfcRelAssignsToActor"):
actor = rel.RelatingActor
if actor.TheActor.is_a("IfcPerson"):
roles = cls.get_roles(actor.TheActor)
elif actor.TheActor.is_a("IfcOrganization"):
roles = cls.get_roles(actor.TheActor)
elif actor.TheActor.is_a("IfcPersonAndOrganization"):
roles = cls.get_roles(actor.TheActor.ThePerson)
roles.extend(cls.get_roles(actor.TheActor.TheOrganization))
role = ", ".join(roles)
results.append({
"id": actor.id(), "name": actor.Name or "Unnamed", "role": role, "ifc_class": actor.is_a()
})
return results
@classmethod
def get_roles(cls, parent):
return [r.UserDefinedRole or r.Role for r in parent.Roles or []]
@@ -355,3 +355,27 @@ class RemoveActor(bpy.types.Operator, Operator):
def _execute(self, context):
core.remove_actor(tool.Ifc, actor=tool.Ifc.get().by_id(self.actor))
class AssignActor(bpy.types.Operator, Operator):
bl_idname = "bim.assign_actor"
bl_label = "Assign Actor"
bl_options = {"REGISTER", "UNDO"}
actor: bpy.props.IntProperty()
def _execute(self, context):
core.assign_actor(
tool.Ifc, actor=tool.Ifc.get().by_id(self.actor), element=tool.Ifc.get_entity(context.active_object)
)
class UnassignActor(bpy.types.Operator, Operator):
bl_idname = "bim.unassign_actor"
bl_label = "Unassign Actor"
bl_options = {"REGISTER", "UNDO"}
actor: bpy.props.IntProperty()
def _execute(self, context):
core.unassign_actor(
tool.Ifc, actor=tool.Ifc.get().by_id(self.actor), element=tool.Ifc.get_entity(context.active_object)
)
@@ -18,7 +18,7 @@
import bpy
from blenderbim.bim.prop import StrProperty, Attribute
from blenderbim.bim.module.owner.data import OwnerData, ActorData
from blenderbim.bim.module.owner.data import OwnerData, ActorData, ObjectActorData
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
@@ -50,6 +50,12 @@ def get_the_actor(self, context):
return ActorData.data["the_actor"]
def get_actor(self, context):
if not ObjectActorData.is_loaded:
ObjectActorData.load()
return ObjectActorData.data["actor"]
def update_actor_type(self, context):
ActorData.data["the_actor"] = ActorData.the_actor()
@@ -95,3 +101,4 @@ class BIMOwnerProperties(PropertyGroup):
update=update_actor_type,
)
the_actor: EnumProperty(items=get_the_actor, name="Actor")
actor: EnumProperty(items=get_actor, name="Actor")
@@ -19,7 +19,7 @@
import bpy
import blenderbim.bim.helper
import blenderbim.tool as tool
from blenderbim.bim.module.owner.data import PeopleData, OrganisationsData, OwnerData, ActorData
from blenderbim.bim.module.owner.data import PeopleData, OrganisationsData, OwnerData, ActorData, ObjectActorData
def draw_roles(box, parent):
@@ -281,3 +281,38 @@ class BIM_PT_actor(bpy.types.Panel):
row.label(text=actor["the_actor"])
row.operator("bim.enable_editing_actor", icon="GREASEPENCIL", text="").actor = actor["id"]
row.operator("bim.remove_actor", icon="X", text="").actor = actor["id"]
class BIM_PT_object_actor(bpy.types.Panel):
bl_label = "IFC Actor"
bl_idname = "BIM_PT_object_actor"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_misc_object"
@classmethod
def poll(cls, context):
return tool.Ifc.get()
def draw(self, context):
if not ObjectActorData.is_loaded:
ObjectActorData.load()
self.props = context.scene.BIMOwnerProperties
if not ObjectActorData.data["actor"]:
row = self.layout.row(align=True)
row.label(text="No Actors Found", icon="USER")
return
row = self.layout.row(align=True)
row.prop(self.props, "actor", text="")
row.operator("bim.assign_actor", icon="ADD", text="").actor = int(self.props.actor)
for actor in ObjectActorData.data["actors"]:
row = self.layout.row(align=True)
row.label(text=actor["name"], icon="USER")
row.label(text=actor["role"])
row.operator("bim.unassign_actor", icon="X", text="").actor = actor["id"]
+8
View File
@@ -165,3 +165,11 @@ def disable_editing_actor(owner):
def edit_actor(ifc, owner):
ifc.run("owner.edit_actor", actor=owner.get_actor(), attributes=owner.export_actor_attributes())
disable_editing_actor(owner)
def assign_actor(ifc, actor=None, element=None):
ifc.run("owner.assign_actor", relating_actor=actor, related_object=element)
def unassign_actor(ifc, actor=None, element=None):
ifc.run("owner.unassign_actor", relating_actor=actor, related_object=element)
@@ -277,3 +277,30 @@ Scenario: Edit actor
And I press "bim.enable_editing_actor(actor={actor})"
When I press "bim.edit_actor"
Then "scene.BIMOwnerProperties.active_actor_id" is "0"
Scenario: Assign actor
Given an empty IFC project
And I press "bim.add_person"
And I press "bim.add_actor"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the variable "actor" is "{ifc}.by_type('IfcActor')[0].id()"
And the object "IfcWall/Cube" is selected
When I press "bim.assign_actor(actor={actor})"
Then nothing happens
Scenario: Unassign actor
Given an empty IFC project
And I press "bim.add_person"
And I press "bim.add_actor"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the variable "actor" is "{ifc}.by_type('IfcActor')[0].id()"
And the object "IfcWall/Cube" is selected
And I press "bim.assign_actor(actor={actor})"
When I press "bim.unassign_actor(actor={actor})"
Then nothing happens
+12
View File
@@ -259,3 +259,15 @@ class TestEditActor:
ifc.run("owner.edit_actor", actor="actor", attributes="attributes").should_be_called()
owner.clear_actor().should_be_called()
subject.edit_actor(ifc, owner)
class TestAssignActor:
def test_run(self, ifc, owner):
ifc.run("owner.assign_actor", relating_actor="actor", related_object="element").should_be_called()
subject.assign_actor(ifc, actor="actor", element="element")
class TestUnassignActor:
def test_run(self, ifc, owner):
ifc.run("owner.unassign_actor", relating_actor="actor", related_object="element").should_be_called()
subject.unassign_actor(ifc, actor="actor", element="element")
@@ -0,0 +1,59 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {
"relating_actor": None,
"related_object": None,
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
if self.settings["related_object"].HasAssignments:
for rel in self.settings["related_object"].HasAssignments:
if rel.is_a("IfcRelAssignsToActor") and rel.RelatingActor == self.settings["relating_actor"]:
return
rel = None
if self.settings["relating_actor"].IsActingUpon:
rel = self.settings["relating_actor"].IsActingUpon[0]
if rel:
related_objects = list(rel.RelatedObjects)
related_objects.append(self.settings["related_object"])
rel.RelatedObjects = related_objects
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
else:
rel = self.file.create_entity(
"IfcRelAssignsToActor",
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
"RelatedObjects": [self.settings["related_object"]],
"RelatingActor": self.settings["relating_actor"],
}
)
return rel
@@ -0,0 +1,43 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {
"relating_actor": None,
"related_object": None,
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
for rel in self.settings["related_object"].HasAssignments or []:
if not rel.is_a("IfcRelAssignsToActor") or rel.RelatingActor != self.settings["relating_actor"]:
continue
if len(rel.RelatedObjects) == 1:
return self.file.remove(rel)
related_objects = list(rel.RelatedObjects)
related_objects.remove(self.settings["related_object"])
rel.RelatedObjects = related_objects
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
return rel
@@ -0,0 +1,38 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import test.bootstrap
import ifcopenshell.api
class TestAssignProduct(test.bootstrap.IFC4):
def test_assigning_an_actor(self):
wall = self.file.createIfcWall()
wall2 = self.file.createIfcWall()
actor = self.file.createIfcActor()
ifcopenshell.api.run("owner.assign_actor", self.file, relating_actor=actor, related_object=wall)
assert actor.IsActingUpon[0].RelatedObjects == (wall,)
ifcopenshell.api.run("owner.assign_actor", self.file, relating_actor=actor, related_object=wall2)
assert actor.IsActingUpon[0].RelatedObjects == (wall, wall2)
def test_not_assigning_twice(self):
wall = self.file.createIfcWall()
actor = self.file.createIfcActor()
ifcopenshell.api.run("owner.assign_actor", self.file, relating_actor=actor, related_object=wall)
ifcopenshell.api.run("owner.assign_actor", self.file, relating_actor=actor, related_object=wall)
assert actor.IsActingUpon[0].RelatedObjects == (wall,)
@@ -0,0 +1,29 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import test.bootstrap
import ifcopenshell.api
class TestUnassignActor(test.bootstrap.IFC4):
def test_unassigning_an_actor(self):
wall = self.file.createIfcWall()
actor = self.file.createIfcActor()
ifcopenshell.api.run("owner.assign_actor", self.file, relating_actor=actor, related_object=wall)
ifcopenshell.api.run("owner.unassign_actor", self.file, relating_actor=actor, related_object=wall)
assert len(self.file.by_type("IfcRelAssignsToActor")) == 0