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.AddPersonAndOrganisation,
operator.AddPersonAttribute, operator.AddPersonAttribute,
operator.AddRole, operator.AddRole,
operator.AssignActor,
operator.ClearUser, operator.ClearUser,
operator.DisableEditingActor, operator.DisableEditingActor,
operator.DisableEditingAddress, operator.DisableEditingAddress,
@@ -53,11 +54,13 @@ classes = (
operator.RemovePersonAttribute, operator.RemovePersonAttribute,
operator.RemoveRole, operator.RemoveRole,
operator.SetUser, operator.SetUser,
operator.UnassignActor,
prop.BIMOwnerProperties, prop.BIMOwnerProperties,
ui.BIM_PT_people, ui.BIM_PT_people,
ui.BIM_PT_organisations, ui.BIM_PT_organisations,
ui.BIM_PT_owner, ui.BIM_PT_owner,
ui.BIM_PT_actor, ui.BIM_PT_actor,
ui.BIM_PT_object_actor,
) )
@@ -25,6 +25,7 @@ def refresh():
OrganisationsData.is_loaded = False OrganisationsData.is_loaded = False
OwnerData.is_loaded = False OwnerData.is_loaded = False
ActorData.is_loaded = False ActorData.is_loaded = False
ObjectActorData.is_loaded = False
class RolesAddressesData: class RolesAddressesData:
@@ -243,3 +244,46 @@ class ActorData:
{"id": actor.id(), "name": actor.Name or "Unnamed", "the_actor": the_actor, "is_editing": is_editing} {"id": actor.id(), "name": actor.Name or "Unnamed", "the_actor": the_actor, "is_editing": is_editing}
) )
return actors 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): def _execute(self, context):
core.remove_actor(tool.Ifc, actor=tool.Ifc.get().by_id(self.actor)) 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 import bpy
from blenderbim.bim.prop import StrProperty, Attribute 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.types import PropertyGroup
from bpy.props import ( from bpy.props import (
PointerProperty, PointerProperty,
@@ -50,6 +50,12 @@ def get_the_actor(self, context):
return ActorData.data["the_actor"] 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): def update_actor_type(self, context):
ActorData.data["the_actor"] = ActorData.the_actor() ActorData.data["the_actor"] = ActorData.the_actor()
@@ -95,3 +101,4 @@ class BIMOwnerProperties(PropertyGroup):
update=update_actor_type, update=update_actor_type,
) )
the_actor: EnumProperty(items=get_the_actor, name="Actor") the_actor: EnumProperty(items=get_the_actor, name="Actor")
actor: EnumProperty(items=get_actor, name="Actor")
@@ -19,7 +19,7 @@
import bpy import bpy
import blenderbim.bim.helper import blenderbim.bim.helper
import blenderbim.tool as tool 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): def draw_roles(box, parent):
@@ -281,3 +281,38 @@ class BIM_PT_actor(bpy.types.Panel):
row.label(text=actor["the_actor"]) row.label(text=actor["the_actor"])
row.operator("bim.enable_editing_actor", icon="GREASEPENCIL", text="").actor = actor["id"] row.operator("bim.enable_editing_actor", icon="GREASEPENCIL", text="").actor = actor["id"]
row.operator("bim.remove_actor", icon="X", 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): def edit_actor(ifc, owner):
ifc.run("owner.edit_actor", actor=owner.get_actor(), attributes=owner.export_actor_attributes()) ifc.run("owner.edit_actor", actor=owner.get_actor(), attributes=owner.export_actor_attributes())
disable_editing_actor(owner) 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})" And I press "bim.enable_editing_actor(actor={actor})"
When I press "bim.edit_actor" When I press "bim.edit_actor"
Then "scene.BIMOwnerProperties.active_actor_id" is "0" 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() ifc.run("owner.edit_actor", actor="actor", attributes="attributes").should_be_called()
owner.clear_actor().should_be_called() owner.clear_actor().should_be_called()
subject.edit_actor(ifc, owner) 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