You can now array IFC elements parametrically

This commit is contained in:
Dion Moult
2022-10-27 23:35:51 +11:00
parent f2c2fa8dce
commit 62229b032e
7 changed files with 337 additions and 4 deletions
@@ -17,9 +17,15 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from . import handler, prop, ui, grid, product, wall, slab, stair, opening, pie, workspace, profile
from . import handler, prop, ui, grid, array, product, wall, slab, stair, opening, pie, workspace, profile
classes = (
array.AddArray,
array.DisableEditingArray,
array.EditArray,
array.EnableEditingArray,
array.RemoveArray,
array.SelectArrayParent,
product.AddConstrTypeInstance,
product.AddEmptyType,
product.AlignProduct,
@@ -66,7 +72,9 @@ classes = (
prop.ConstrClassInfo,
prop.ConstrBrowserState,
prop.BIMModelProperties,
prop.BIMArrayProperties,
ui.BIM_PT_authoring,
ui.BIM_PT_array,
ui.DisplayConstrTypesUI,
ui.LaunchTypeManager,
ui.HelpConstrTypes,
@@ -87,6 +95,7 @@ def register():
if not bpy.app.background:
bpy.utils.register_tool(workspace.BimTool, after={"builtin.scale_cage"}, separator=True, group=True)
bpy.types.Scene.BIMModelProperties = bpy.props.PointerProperty(type=prop.BIMModelProperties)
bpy.types.Object.BIMArrayProperties = bpy.props.PointerProperty(type=prop.BIMArrayProperties)
bpy.types.VIEW3D_MT_mesh_add.append(grid.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(stair.add_object_button)
bpy.types.VIEW3D_MT_add.append(ui.add_menu)
@@ -103,6 +112,7 @@ def unregister():
if not bpy.app.background:
bpy.utils.unregister_tool(workspace.BimTool)
del bpy.types.Scene.BIMModelProperties
del bpy.types.Object.BIMArrayProperties
bpy.app.handlers.load_post.remove(handler.load_post)
bpy.types.VIEW3D_MT_mesh_add.remove(grid.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.remove(stair.add_object_button)
@@ -0,0 +1,176 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2022 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 json
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element
import blenderbim.tool as tool
from mathutils import Vector
class AddArray(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_array"
bl_label = "Add Array"
bl_options = {"REGISTER"}
def _execute(self, context):
obj = context.active_object
element = tool.Ifc.get_entity(obj)
array = {"children": [], "count": 1, "x": 0.0, "y": 0.0, "z": 0.0}
psets = ifcopenshell.util.element.get_psets(element)
pset = psets.get("BBIM_Array", None)
if pset:
data = json.loads(pset["Data"])
data.append(array)
pset = tool.Ifc.get().by_id(pset["id"])
else:
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="BBIM_Array")
data = [array]
ifcopenshell.api.run(
"pset.edit_pset",
tool.Ifc.get(),
pset=pset,
properties={"Parent": element.GlobalId, "Data": json.dumps(data)},
)
return {"FINISHED"}
class DisableEditingArray(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.disable_editing_array"
bl_label = "Disable Editing Array"
bl_options = {"REGISTER"}
def _execute(self, context):
props = context.active_object.BIMArrayProperties.is_editing = -1
return {"FINISHED"}
class EditArray(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_array"
bl_label = "Edit Array"
bl_options = {"REGISTER"}
item: bpy.props.IntProperty()
def _execute(self, context):
obj = context.active_object
element = tool.Ifc.get_entity(obj)
props = obj.BIMArrayProperties
psets = ifcopenshell.util.element.get_psets(element)
pset = psets["BBIM_Array"]
data = json.loads(pset["Data"])
data[self.item] = {
"children": data[self.item]["children"],
"count": props.count,
"x": props.x,
"y": props.y,
"z": props.z,
}
props.is_editing = -1
try:
parent = tool.Ifc.get_object(tool.Ifc.get().by_guid(pset["Parent"]))
except:
return {"FINISHED"}
tool.Model.regenerate_array(parent, data)
pset = tool.Ifc.get().by_id(pset["id"])
data = json.dumps(data)
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": data})
return {"FINISHED"}
class EnableEditingArray(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.enable_editing_array"
bl_label = "Enable Editing Array"
bl_options = {"REGISTER"}
item: bpy.props.IntProperty()
def _execute(self, context):
obj = context.active_object
element = tool.Ifc.get_entity(obj)
psets = ifcopenshell.util.element.get_psets(element)
data = json.loads(psets["BBIM_Array"]["Data"])[self.item]
props = obj.BIMArrayProperties
props.count = data["count"]
props.x = data["x"]
props.y = data["y"]
props.z = data["z"]
props.is_editing = self.item
class RemoveArray(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_array"
bl_label = "Remove Array"
bl_options = {"REGISTER"}
item: bpy.props.IntProperty()
def _execute(self, context):
obj = context.active_object
element = tool.Ifc.get_entity(obj)
props = obj.BIMArrayProperties
psets = ifcopenshell.util.element.get_psets(element)
pset = psets["BBIM_Array"]
data = json.loads(pset["Data"])
data[self.item]["count"] = 1
props.is_editing = -1
try:
parent = tool.Ifc.get_object(tool.Ifc.get().by_guid(pset["Parent"]))
except:
return {"FINISHED"}
tool.Model.regenerate_array(parent, data)
pset = tool.Ifc.get().by_id(pset["id"])
if len(data) == 1:
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
else:
del data[self.item]
data = json.dumps(data)
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": data})
return {"FINISHED"}
class SelectArrayParent(bpy.types.Operator):
bl_idname = "bim.select_array_parent"
bl_label = "Select Array Parent"
bl_options = {"REGISTER", "UNDO"}
parent: bpy.props.StringProperty()
def execute(self, context):
try:
element = tool.Ifc.get().by_guid(self.parent)
except:
return {"FINISHED"}
obj = tool.Ifc.get_object(element)
if obj:
context.view_layer.objects.active = obj
obj.select_set(True)
return {"FINISHED"}
@@ -18,8 +18,10 @@
import bpy
import math
import json
import functools
import ifcopenshell
import ifcopenshell.util.element
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.model.root import ConstrTypeEntityNotFound
@@ -28,6 +30,7 @@ from blenderbim.bim.prop import get_ifc_entity_description, get_predefined_type_
def refresh():
AuthoringData.is_loaded = False
ArrayData.is_loaded = False
class AuthoringData:
@@ -316,3 +319,29 @@ class AuthoringData:
def relating_type_id_by_name(cls, ifc_class, relating_type):
relating_types = [ct[0] for ct in cls.relating_types(ifc_class=ifc_class) if ct[1] == relating_type]
return None if len(relating_types) == 0 else relating_types[0]
class ArrayData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.is_loaded = True
cls.data = { "parameters": cls.parameters() }
@classmethod
def parameters(cls):
element = tool.Ifc.get_entity(bpy.context.active_object)
if element:
psets = ifcopenshell.util.element.get_psets(element)
parameters = psets.get("BBIM_Array", None)
if parameters:
try:
parent = tool.Ifc.get().by_guid(parameters["Parent"])
parameters["has_parent"] = True
parameters["parent_name"] = parent.Name or "Unnamed"
parameters["data"] = json.loads(parameters.get("Data", "[]") or "[]")
except:
parameters["has_parent"] = False
return parameters
@@ -1,5 +1,5 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
# Copyright (C) 2020, 2021, 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
@@ -245,3 +245,11 @@ class BIMModelProperties(PropertyGroup):
)
type_class: bpy.props.EnumProperty(items=get_type_class, name="IFC Class", update=update_type_class)
type_predefined_type: bpy.props.EnumProperty(items=get_type_predefined_type, name="Predefined Type", default=None)
class BIMArrayProperties(PropertyGroup):
is_editing: bpy.props.IntProperty(default=-1)
count: bpy.props.IntProperty(name="Count", default=0)
x: bpy.props.FloatProperty(name="X", default=0)
y: bpy.props.FloatProperty(name="Y", default=0)
z: bpy.props.FloatProperty(name="Z", default=0)
@@ -1,5 +1,5 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
# Copyright (C) 2020, 2021, 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
@@ -17,8 +17,9 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import blenderbim.tool as tool
from bpy.types import Panel, Operator, Menu
from blenderbim.bim.module.model.data import AuthoringData
from blenderbim.bim.module.model.data import AuthoringData, ArrayData
from blenderbim.bim.module.model.prop import store_cursor_position
from blenderbim.bim.helper import prop_with_search
@@ -254,6 +255,57 @@ class HelpConstrTypes(Operator):
]
class BIM_PT_array(bpy.types.Panel):
bl_label = "IFC Array"
bl_idname = "BIM_PT_array"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "modifier"
@classmethod
def poll(cls, context):
return tool.Ifc.get() and tool.Ifc.get_entity(context.active_object)
def draw(self, context):
if not ArrayData.is_loaded:
ArrayData.load()
props = context.active_object.BIMArrayProperties
if ArrayData.data["parameters"]:
row = self.layout.row(align=True)
row.label(text=ArrayData.data["parameters"]["parent_name"], icon="CON_CHILDOF")
op = row.operator("bim.select_array_parent", icon="OBJECT_DATA", text="")
op.parent = ArrayData.data["parameters"]["Parent"]
if ArrayData.data["parameters"]["data"]:
row.operator("bim.add_array", icon="ADD", text="")
for i, array in enumerate(ArrayData.data["parameters"]["data"]):
box = self.layout.box()
if props.is_editing == i:
row = box.row(align=True)
row.prop(props, "count", icon="MOD_ARRAY")
row.operator("bim.edit_array", icon="CHECKMARK", text="").item = i
row.operator("bim.disable_editing_array", icon="CANCEL", text="")
row = box.row(align=True)
row.prop(props, "x")
row.prop(props, "y")
row.prop(props, "z")
else:
row = box.row(align=True)
row.label(text=f"{array['count']} Items", icon="MOD_ARRAY")
row.operator("bim.enable_editing_array", icon="GREASEPENCIL", text="").item = i
row.operator("bim.remove_array", icon="X", text="").item = i
row = box.row(align=True)
row.label(text=f"X: {array['x']}")
row.label(text=f"Y: {array['y']}")
row.label(text=f"Z: {array['z']}")
else:
row = self.layout.row()
row.label(text="No Array Found")
row.operator("bim.add_array", icon="ADD", text="")
class BIM_MT_model(Menu):
bl_idname = "BIM_MT_model"
bl_label = "IFC Objects"
+58
View File
@@ -139,3 +139,61 @@ class Model(blenderbim.core.tool.Model):
(obj.matrix_world @ Vector((max_x, 0.0, 0.0))).to_2d(),
]
return axes
@classmethod
def regenerate_array(cls, parent, data):
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
obj_stack = [parent]
for array in data:
child_i = 0
existing_children = set(array["children"])
total_existing_children = len(array["children"])
children_elements = []
children_objs = []
for i in range(0, array["count"]):
if i == 0:
continue
offset = Vector((i * array["x"] * unit_scale, i * array["y"] * unit_scale, i * array["z"] * unit_scale))
for obj in obj_stack:
if child_i >= total_existing_children:
child_obj = tool.Spatial.duplicate_object_and_data(obj)
child_element = tool.Spatial.run_root_copy_class(obj=child_obj)
else:
global_id = array["children"][child_i]
try:
child_element = tool.Ifc.get().by_guid(global_id)
child_obj = tool.Ifc.get_object(child_element)
assert child_obj
except:
child_obj = tool.Spatial.duplicate_object_and_data(obj)
child_element = tool.Spatial.run_root_copy_class(obj=child_obj)
child_psets = ifcopenshell.util.element.get_psets(child_element)
child_pset = child_psets.get("BBIM_Array")
if child_pset:
ifcopenshell.api.run(
"pset.edit_pset",
tool.Ifc.get(),
product=child_element,
pset=tool.Ifc.get().by_id(child_pset["id"]),
properties={"Data": None},
)
new_matrix = obj.matrix_world.copy()
new_matrix.col[3] = (obj.matrix_world.col[3].to_3d() + offset).to_4d()
child_obj.matrix_world = new_matrix
children_objs.append(child_obj)
children_elements.append(child_element)
child_i += 1
obj_stack.extend(children_objs)
array["children"] = [e.GlobalId for e in children_elements]
removed_children = set(existing_children) - set(array["children"])
for removed_child in removed_children:
element = tool.Ifc.get().by_guid(removed_child)
obj = tool.Ifc.get_object(element)
if obj:
bpy.data.objects.remove(obj)
# TODO: Not sufficient, refactor OverrideDeleteTrait
bpy.context.view_layer.update()