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/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy 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 = ( classes = (
array.AddArray,
array.DisableEditingArray,
array.EditArray,
array.EnableEditingArray,
array.RemoveArray,
array.SelectArrayParent,
product.AddConstrTypeInstance, product.AddConstrTypeInstance,
product.AddEmptyType, product.AddEmptyType,
product.AlignProduct, product.AlignProduct,
@@ -66,7 +72,9 @@ classes = (
prop.ConstrClassInfo, prop.ConstrClassInfo,
prop.ConstrBrowserState, prop.ConstrBrowserState,
prop.BIMModelProperties, prop.BIMModelProperties,
prop.BIMArrayProperties,
ui.BIM_PT_authoring, ui.BIM_PT_authoring,
ui.BIM_PT_array,
ui.DisplayConstrTypesUI, ui.DisplayConstrTypesUI,
ui.LaunchTypeManager, ui.LaunchTypeManager,
ui.HelpConstrTypes, ui.HelpConstrTypes,
@@ -87,6 +95,7 @@ def register():
if not bpy.app.background: if not bpy.app.background:
bpy.utils.register_tool(workspace.BimTool, after={"builtin.scale_cage"}, separator=True, group=True) 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.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(grid.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(stair.add_object_button) bpy.types.VIEW3D_MT_mesh_add.append(stair.add_object_button)
bpy.types.VIEW3D_MT_add.append(ui.add_menu) bpy.types.VIEW3D_MT_add.append(ui.add_menu)
@@ -103,6 +112,7 @@ def unregister():
if not bpy.app.background: if not bpy.app.background:
bpy.utils.unregister_tool(workspace.BimTool) bpy.utils.unregister_tool(workspace.BimTool)
del bpy.types.Scene.BIMModelProperties del bpy.types.Scene.BIMModelProperties
del bpy.types.Object.BIMArrayProperties
bpy.app.handlers.load_post.remove(handler.load_post) 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(grid.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.remove(stair.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 bpy
import math import math
import json
import functools import functools
import ifcopenshell import ifcopenshell
import ifcopenshell.util.element
import blenderbim.tool as tool import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.model.root import ConstrTypeEntityNotFound 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(): def refresh():
AuthoringData.is_loaded = False AuthoringData.is_loaded = False
ArrayData.is_loaded = False
class AuthoringData: class AuthoringData:
@@ -316,3 +319,29 @@ class AuthoringData:
def relating_type_id_by_name(cls, ifc_class, relating_type): 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] 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] 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 # 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. # 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_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) 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 # 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. # 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/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy import bpy
import blenderbim.tool as tool
from bpy.types import Panel, Operator, Menu 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.module.model.prop import store_cursor_position
from blenderbim.bim.helper import prop_with_search 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): class BIM_MT_model(Menu):
bl_idname = "BIM_MT_model" bl_idname = "BIM_MT_model"
bl_label = "IFC Objects" 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(), (obj.matrix_world @ Vector((max_x, 0.0, 0.0))).to_2d(),
] ]
return axes 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()