mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
You can now manage actor assignments to products
This commit is contained in:
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user