mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
You can now array IFC elements parametrically
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user