mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-13 19:07:57 +00:00
Ifc Railing modifier
Added Ifc Railing modifier (similarly to stair, window, door modifiers...). You can start working with it either creating some edge based path and adding this modifier or by creating Railing for Shift+A menu (it will create some simple path to start with). In railing modifier you can switch two edit modes - editing main railing properties and editing path. Editing path will show only the railing path, you can edit it and save it. You cannot edit the railing pass if you're not in this mode.
This commit is contained in:
@@ -36,6 +36,7 @@ from . import (
|
||||
profile,
|
||||
sverchok_modifier,
|
||||
door,
|
||||
railing
|
||||
)
|
||||
|
||||
classes = (
|
||||
@@ -99,12 +100,14 @@ classes = (
|
||||
prop.BIMSverchokProperties,
|
||||
prop.BIMWindowProperties,
|
||||
prop.BIMDoorProperties,
|
||||
prop.BIMRailingProperties,
|
||||
ui.BIM_PT_authoring,
|
||||
ui.BIM_PT_array,
|
||||
ui.BIM_PT_stair,
|
||||
ui.BIM_PT_sverchok,
|
||||
ui.BIM_PT_window,
|
||||
ui.BIM_PT_door,
|
||||
ui.BIM_PT_railing,
|
||||
ui.LaunchTypeManager,
|
||||
ui.HelpConstrTypes,
|
||||
ui.BIM_MT_model,
|
||||
@@ -138,6 +141,15 @@ classes = (
|
||||
door.FinishEditingDoor,
|
||||
door.EnableEditingDoor,
|
||||
door.RemoveDoor,
|
||||
railing.BIM_OT_add_railing,
|
||||
railing.AddRailing,
|
||||
railing.CancelEditingRailing,
|
||||
railing.FinishEditingRailing,
|
||||
railing.EnableEditingRailing,
|
||||
railing.CancelEditingRailingPath,
|
||||
railing.FinishEditingRailingPath,
|
||||
railing.EnableEditingRailingPath,
|
||||
railing.RemoveRailing,
|
||||
)
|
||||
|
||||
addon_keymaps = []
|
||||
@@ -152,10 +164,12 @@ def register():
|
||||
bpy.types.Object.BIMSverchokProperties = bpy.props.PointerProperty(type=prop.BIMSverchokProperties)
|
||||
bpy.types.Object.BIMWindowProperties = bpy.props.PointerProperty(type=prop.BIMWindowProperties)
|
||||
bpy.types.Object.BIMDoorProperties = bpy.props.PointerProperty(type=prop.BIMDoorProperties)
|
||||
bpy.types.Object.BIMRailingProperties = bpy.props.PointerProperty(type=prop.BIMRailingProperties)
|
||||
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(window.add_object_button)
|
||||
bpy.types.VIEW3D_MT_mesh_add.append(door.add_object_button)
|
||||
bpy.types.VIEW3D_MT_mesh_add.append(railing.add_object_button)
|
||||
bpy.types.VIEW3D_MT_add.append(ui.add_menu)
|
||||
bpy.app.handlers.load_post.append(handler.load_post)
|
||||
wm = bpy.context.window_manager
|
||||
@@ -175,11 +189,13 @@ def unregister():
|
||||
del bpy.types.Object.BIMSverchokProperties
|
||||
del bpy.types.Object.BIMWindowProperties
|
||||
del bpy.types.Object.BIMDoorProperties
|
||||
del bpy.types.Object.BIMRailingProperties
|
||||
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)
|
||||
bpy.types.VIEW3D_MT_mesh_add.remove(window.add_object_button)
|
||||
bpy.types.VIEW3D_MT_mesh_add.remove(door.add_object_button)
|
||||
bpy.types.VIEW3D_MT_mesh_add.remove(railing.add_object_button)
|
||||
bpy.types.VIEW3D_MT_add.remove(ui.add_menu)
|
||||
wm = bpy.context.window_manager
|
||||
kc = wm.keyconfigs.addon
|
||||
|
||||
@@ -35,6 +35,7 @@ def refresh():
|
||||
SverchokData.is_loaded = False
|
||||
WindowData.is_loaded = False
|
||||
DoorData.is_loaded = False
|
||||
RailingData.is_loaded = False
|
||||
|
||||
|
||||
class AuthoringData:
|
||||
@@ -462,3 +463,23 @@ class DoorData:
|
||||
if parameters:
|
||||
parameters["data_dict"] = json.loads(parameters.get("Data", "[]") or "[]")
|
||||
return parameters
|
||||
|
||||
|
||||
class RailingData:
|
||||
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_Railing", None)
|
||||
if parameters:
|
||||
parameters["data_dict"] = json.loads(parameters.get("Data", "[]") or "[]")
|
||||
return parameters
|
||||
|
||||
@@ -542,3 +542,26 @@ class BIMDoorProperties(PropertyGroup):
|
||||
kwargs["frame_depth"] = self.frame_depth
|
||||
|
||||
return kwargs
|
||||
|
||||
|
||||
class BIMRailingProperties(PropertyGroup):
|
||||
railing_types = (
|
||||
("FRAMELESS_PANEL", "FRAMELESS_PANEL", ""),
|
||||
)
|
||||
|
||||
railing_added_previously: bpy.props.BoolProperty(default=False)
|
||||
is_editing: bpy.props.IntProperty(default=-1)
|
||||
is_editing_path: bpy.props.BoolProperty(default=False)
|
||||
|
||||
railing_type: bpy.props.EnumProperty(name="Railing Type", items=railing_types, default="FRAMELESS_PANEL")
|
||||
height: bpy.props.FloatProperty(name="Height", default=1.0)
|
||||
thickness: bpy.props.FloatProperty(name="Thickness", default=0.050)
|
||||
spacing: bpy.props.FloatProperty(name="Spacing", default=0.050)
|
||||
|
||||
def get_general_kwargs(self):
|
||||
return {
|
||||
"railing_type": self.railing_type,
|
||||
"height": self.height,
|
||||
"thickness": self.thickness,
|
||||
"spacing": self.spacing,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,405 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2023 @Andrej730
|
||||
#
|
||||
# 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
|
||||
from bpy.types import Operator
|
||||
import bmesh
|
||||
|
||||
import ifcopenshell
|
||||
from ifcopenshell.util.shape_builder import V
|
||||
import blenderbim
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.geometry as core
|
||||
from blenderbim.bim.helper import convert_property_group_from_si
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.model.door import bm_sort_out_geom
|
||||
from blenderbim.bim.module.model.data import RailingData, refresh
|
||||
|
||||
from mathutils import Vector, Matrix
|
||||
from pprint import pprint
|
||||
import json
|
||||
|
||||
# reference:
|
||||
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcRailing.htm
|
||||
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcRailingType.htm
|
||||
|
||||
def bm_split_edge_at_offset(edge, offset):
|
||||
v0, v1 = edge.verts
|
||||
|
||||
offset = offset / 2
|
||||
edge_len = (v0.co-v1.co).xy.length
|
||||
|
||||
split_output_0 = bmesh.utils.edge_split(edge, v0, offset/edge_len)
|
||||
split_output_1 = bmesh.utils.edge_split(edge, v1, offset/(edge_len-offset))
|
||||
new_geometry = bm_sort_out_geom(split_output_0 + split_output_1)
|
||||
return new_geometry
|
||||
|
||||
def update_railing_modifier_ifc_data(context):
|
||||
obj = context.active_object
|
||||
props = obj.BIMRailingProperties
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
ifc_file = tool.Ifc.get()
|
||||
|
||||
# type attributes
|
||||
element.PredefinedType = 'USERDEFINED'
|
||||
# occurences attributes
|
||||
occurences = tool.Ifc.get_all_element_occurences(element)
|
||||
for occurence in occurences:
|
||||
occurence.ObjectType = props.railing_type
|
||||
|
||||
# update pset
|
||||
pset_common = tool.Pset.get_element_pset(element, "Pset_RailingCommon")
|
||||
if not pset_common:
|
||||
pset_common = ifcopenshell.api.run("pset.add_pset", ifc_file, product=element, name="Pset_RailingCommon")
|
||||
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset",
|
||||
ifc_file,
|
||||
pset=pset_common,
|
||||
properties={
|
||||
"Height": props.height,
|
||||
},
|
||||
)
|
||||
|
||||
def update_bbim_railing_pset(element, railing_data):
|
||||
pset = tool.Pset.get_element_pset(element, "BBIM_Railing")
|
||||
if not pset:
|
||||
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="BBIM_Railing")
|
||||
railing_data = json.dumps(railing_data, default=list)
|
||||
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": railing_data})
|
||||
|
||||
def update_railing_modifier_bmesh(context):
|
||||
obj = context.object
|
||||
props = obj.BIMRailingProperties
|
||||
|
||||
if not RailingData.is_loaded:
|
||||
RailingData.load()
|
||||
path_data = RailingData.data["parameters"]['data_dict']['path_data']
|
||||
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
# need to make sure we support edit mode
|
||||
# since users will probably be in edit mode when they'll be changing railing path
|
||||
bm = tool.Blender.get_bmesh_for_mesh(obj.data, clean=True)
|
||||
|
||||
# generating railing path
|
||||
bm.verts.index_update()
|
||||
bm.edges.index_update()
|
||||
new_verts = [bm.verts.new(Vector(v) * si_conversion) for v in path_data['verts']]
|
||||
new_edges = [bm.edges.new( (new_verts[e[0]], new_verts[e[1]]) ) for e in path_data['edges']]
|
||||
bm.verts.index_update()
|
||||
bm.edges.index_update()
|
||||
|
||||
if props.is_editing_path:
|
||||
tool.Blender.apply_bmesh(obj.data, bm)
|
||||
return
|
||||
|
||||
# generating the entire railing
|
||||
height = props.height * si_conversion
|
||||
thickness = props.thickness * si_conversion
|
||||
spacing = props.spacing * si_conversion
|
||||
|
||||
# spacing
|
||||
# split each edge in 3 segments by 0.5 * spacing by x-y plane
|
||||
main_edges = bm.edges[:]
|
||||
for main_edge in main_edges:
|
||||
bm_split_edge_at_offset(main_edge, spacing)
|
||||
|
||||
# thickness
|
||||
# keep track of translated verts so we won't translate the same
|
||||
# vert twice
|
||||
edge_dissolving_verts = []
|
||||
for main_edge in main_edges:
|
||||
v0, v1 = main_edge.verts
|
||||
edge_dissolving_verts.extend([v0, v1])
|
||||
|
||||
edge_dir = ( (v1.co-v0.co) * V(1,1,0) ).normalized()
|
||||
ortho_vector = edge_dir.cross( V(0,0,1) )
|
||||
|
||||
extruded_geom = bmesh.ops.extrude_edge_only(bm, edges=[main_edge])['geom']
|
||||
extruded_verts = bm_sort_out_geom(extruded_geom)['verts']
|
||||
bmesh.ops.translate(bm, vec=ortho_vector * (-thickness/2), verts=extruded_verts)
|
||||
|
||||
extruded_geom = bmesh.ops.extrude_edge_only(bm, edges=[main_edge])['geom']
|
||||
extruded_verts = bm_sort_out_geom(extruded_geom)['verts']
|
||||
bmesh.ops.translate(bm, vec=ortho_vector * (thickness/2), verts=extruded_verts)
|
||||
|
||||
# dissolve middle edge
|
||||
bmesh.ops.dissolve_edges(bm, edges=[main_edge])
|
||||
|
||||
# height
|
||||
extruded_geom = bmesh.ops.extrude_face_region(bm, geom=bm.faces)['geom']
|
||||
extruded_verts = bm_sort_out_geom(extruded_geom)['verts']
|
||||
extruded_edges = bm_sort_out_geom(extruded_geom)['edges']
|
||||
extrusion_vector = Vector((0, 0, 1)) * height
|
||||
bmesh.ops.translate(bm, vec=extrusion_vector, verts=extruded_verts)
|
||||
|
||||
# dissolve middle edges
|
||||
edges_to_dissolve = []
|
||||
verts_to_dissolve = []
|
||||
for v in edge_dissolving_verts:
|
||||
for e in v.link_edges:
|
||||
other_vert = e.other_vert(v)
|
||||
if other_vert in extruded_verts:
|
||||
edges_to_dissolve.append(e)
|
||||
verts_to_dissolve.append(other_vert)
|
||||
|
||||
bmesh.ops.dissolve_edges(bm, edges=edges_to_dissolve)
|
||||
bmesh.ops.dissolve_verts(bm, verts=verts_to_dissolve)
|
||||
|
||||
# to remove unnecessary verts in 0 spacing case
|
||||
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001)
|
||||
|
||||
tool.Blender.apply_bmesh(obj.data, bm)
|
||||
|
||||
def get_path_data(obj):
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
if obj.mode == 'EDIT':
|
||||
# otherwise mesh may not contain all changes
|
||||
# added in edit mode
|
||||
obj.update_from_editmode()
|
||||
|
||||
mesh = obj.data
|
||||
path_data = dict()
|
||||
path_data['edges'] = [e.vertices[:] for e in mesh.edges]
|
||||
path_data['verts'] = [v.co / si_conversion for v in mesh.vertices]
|
||||
|
||||
if not path_data['edges'] or not path_data['verts']:
|
||||
return None
|
||||
return path_data
|
||||
|
||||
class BIM_OT_add_railing(Operator):
|
||||
bl_idname = "mesh.add_railing"
|
||||
bl_label = "Railing"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
ifc_file = tool.Ifc.get()
|
||||
if not ifc_file:
|
||||
self.report({"ERROR"}, "You need to start IFC project first to create a railing.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
if context.object is not None:
|
||||
spawn_location = context.object.location.copy()
|
||||
context.object.select_set(False)
|
||||
else:
|
||||
spawn_location = bpy.context.scene.cursor.location.copy()
|
||||
|
||||
mesh = bpy.data.meshes.new("IfcRailing")
|
||||
obj = bpy.data.objects.new("IfcRailing", mesh)
|
||||
obj.location = spawn_location
|
||||
body_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
||||
element = blenderbim.core.root.assign_class(
|
||||
tool.Ifc, tool.Collector, tool.Root, obj=obj, ifc_class="IfcRailing", should_add_representation=False
|
||||
)
|
||||
element.PredefinedType = "USERDEFINED"
|
||||
|
||||
bm = bmesh.new()
|
||||
bm.verts.index_update()
|
||||
bm.edges.index_update()
|
||||
|
||||
new_verts = [bm.verts.new(v) for v in [V(-1, 0, 0), V(0, 0, 0), V(1, 0, 0)]]
|
||||
new_edges = [bm.edges.new( (new_verts[e[0]], new_verts[e[1]]) ) for e in [(0,1), (1,2)]]
|
||||
bm.verts.index_update()
|
||||
bm.edges.index_update()
|
||||
tool.Blender.apply_bmesh(mesh, bm)
|
||||
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
bpy.context.view_layer.objects.active = None
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
bpy.ops.bim.add_railing()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
# UI operators
|
||||
class AddRailing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_railing"
|
||||
bl_label = "Add Railing"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMRailingProperties
|
||||
|
||||
if element.is_a() not in ("IfcRailing", "IfcRailingType"):
|
||||
self.report({"ERROR"}, "Object has to be IfcRailing/IfcRailingType type to add a railing.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
# need to make sure all default props will have correct units
|
||||
if not props.railing_added_previously:
|
||||
skip_props = ("is_editing", "railing_type", "railing_added_previously", "path_data")
|
||||
convert_property_group_from_si(props, skip_props=skip_props)
|
||||
|
||||
railing_data = props.get_general_kwargs()
|
||||
railing_data['path_data'] = get_path_data(obj)
|
||||
|
||||
update_bbim_railing_pset(element, railing_data)
|
||||
update_railing_modifier_ifc_data(context)
|
||||
update_railing_modifier_bmesh(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingRailing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.enable_editing_railing"
|
||||
bl_label = "Enable Editing Railing"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
props = obj.BIMRailingProperties
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Railing", "Data"))
|
||||
data['path_data'] = json.dumps(data['path_data'])
|
||||
|
||||
# required since we could load pset from .ifc and BIMRailingProperties won't be set
|
||||
for prop_name in data:
|
||||
setattr(props, prop_name, data[prop_name])
|
||||
|
||||
# need to make sure all props that weren't used before
|
||||
# will have correct units
|
||||
skip_props = ("is_editing", "railing_type", "railing_added_previously")
|
||||
skip_props += tuple(data.keys())
|
||||
convert_property_group_from_si(props, skip_props=skip_props)
|
||||
|
||||
props.is_editing = 1
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CancelEditingRailing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.cancel_editing_railing"
|
||||
bl_label = "Cancel editing Railing"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Railing", "Data"))
|
||||
props = obj.BIMRailingProperties
|
||||
# restore previous settings since editing was canceled
|
||||
for prop_name in data:
|
||||
setattr(props, prop_name, data[prop_name])
|
||||
|
||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
props.is_editing = -1
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class FinishEditingRailing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.finish_editing_railing"
|
||||
bl_label = "Finish editing railing"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMRailingProperties
|
||||
|
||||
if not RailingData.is_loaded:
|
||||
RailingData.load()
|
||||
path_data = RailingData.data["parameters"]['data_dict']['path_data']
|
||||
|
||||
railing_data = props.get_general_kwargs()
|
||||
railing_data['path_data'] = path_data
|
||||
props.is_editing = -1
|
||||
|
||||
update_bbim_railing_pset(element, railing_data)
|
||||
update_railing_modifier_ifc_data(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingRailingPath(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.enable_editing_railing_path"
|
||||
bl_label = "Enable Editing Railing Path"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
props = obj.BIMRailingProperties
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
|
||||
props.is_editing_path = True
|
||||
update_railing_modifier_bmesh(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CancelEditingRailingPath(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.cancel_editing_railing_path"
|
||||
bl_label = "Cancel Editing Railing Path"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMRailingProperties
|
||||
update_railing_modifier_bmesh(context)
|
||||
props.is_editing_path = False
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class FinishEditingRailingPath(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.finish_editing_railing_path"
|
||||
bl_label = "Finish Editing Railing Path"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMRailingProperties
|
||||
|
||||
railing_data = props.get_general_kwargs()
|
||||
path_data = get_path_data(obj)
|
||||
railing_data['path_data'] = path_data
|
||||
props.is_editing_path = False
|
||||
|
||||
update_bbim_railing_pset(element, railing_data)
|
||||
refresh() # need RailingData to be updated before update_railing_modifier_bmesh
|
||||
update_railing_modifier_bmesh(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveRailing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.remove_railing"
|
||||
bl_label = "Remove Railing"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
props = obj.BIMRailingProperties
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
obj.BIMRailingProperties.is_editing = -1
|
||||
|
||||
pset = tool.Pset.get_element_pset(element, "BBIM_Railing")
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
|
||||
props.railing_added_previously = True
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def add_object_button(self, context):
|
||||
self.layout.operator(BIM_OT_add_railing.bl_idname, icon="PLUGIN")
|
||||
@@ -19,11 +19,12 @@
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
from bpy.types import Panel, Operator, Menu
|
||||
from blenderbim.bim.module.model.data import AuthoringData, ArrayData, StairData, SverchokData, WindowData, DoorData
|
||||
from blenderbim.bim.module.model.data import AuthoringData, ArrayData, StairData, SverchokData, WindowData, DoorData, RailingData
|
||||
from blenderbim.bim.module.model.prop import get_ifc_class
|
||||
from blenderbim.bim.module.model.stair import update_stair_modifier
|
||||
from blenderbim.bim.module.model.window import update_window_modifier_bmesh
|
||||
from blenderbim.bim.module.model.door import update_door_modifier_bmesh
|
||||
from blenderbim.bim.module.model.railing import update_railing_modifier_bmesh
|
||||
|
||||
from blenderbim.bim.helper import prop_with_search
|
||||
|
||||
@@ -529,6 +530,65 @@ class BIM_PT_door(bpy.types.Panel):
|
||||
row.operator("bim.add_door", icon="ADD", text="")
|
||||
|
||||
|
||||
class BIM_PT_railing(bpy.types.Panel):
|
||||
bl_label = "IFC Railing"
|
||||
bl_idname = "BIM_PT_railing"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "modifier"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
# always display modifier if it's IFC object
|
||||
return tool.Ifc.get() and tool.Ifc.get_entity(context.active_object)
|
||||
|
||||
def draw(self, context):
|
||||
if not RailingData.is_loaded:
|
||||
RailingData.load()
|
||||
|
||||
props = context.active_object.BIMRailingProperties
|
||||
|
||||
if RailingData.data["parameters"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Railing parameters", icon="OUTLINER_OB_LATTICE")
|
||||
|
||||
railing_data = RailingData.data["parameters"]["data_dict"]
|
||||
|
||||
if props.is_editing != -1:
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.finish_editing_railing", icon="CHECKMARK", text="Finish editing")
|
||||
row.operator("bim.cancel_editing_railing", icon="CANCEL", text="")
|
||||
|
||||
general_props = props.get_general_kwargs()
|
||||
for prop in general_props:
|
||||
self.layout.prop(props, prop)
|
||||
|
||||
update_railing_modifier_bmesh(context)
|
||||
|
||||
elif props.is_editing_path:
|
||||
row.operator("bim.finish_editing_railing_path", icon="CHECKMARK", text="Finish editing path")
|
||||
row.operator("bim.cancel_editing_railing_path", icon="CANCEL", text="")
|
||||
|
||||
else:
|
||||
row.operator("bim.enable_editing_railing", icon="GREASEPENCIL", text="")
|
||||
row.operator("bim.enable_editing_railing_path", icon="ANIM", text="")
|
||||
row.operator("bim.remove_railing", icon="X", text="")
|
||||
|
||||
box = self.layout.box()
|
||||
general_props = props.get_general_kwargs()
|
||||
for prop in general_props:
|
||||
prop_value = railing_data[prop]
|
||||
prop_value = round(prop_value, 5) if type(prop_value) is float else prop_value
|
||||
row = box.row(align=True)
|
||||
row.label(text=f"{props.bl_rna.properties[prop].name}")
|
||||
row.label(text=str(prop_value))
|
||||
else:
|
||||
row = self.layout.row()
|
||||
row.label(text="No Railing Found")
|
||||
row.operator("bim.add_railing", icon="ADD", text="")
|
||||
|
||||
|
||||
class BIM_MT_model(Menu):
|
||||
bl_idname = "BIM_MT_model"
|
||||
bl_label = "IFC Objects"
|
||||
|
||||
@@ -76,6 +76,9 @@ def get_name(cls, ifc_class, name): pass
|
||||
def get_obj_ifc_definition_id(cls, obj, obj_type): pass
|
||||
def get_selected_objects(cls): pass
|
||||
def set_active_object(cls, obj): pass
|
||||
def apply_bmesh(cls, mesh, bm): pass
|
||||
def get_bmesh_for_mesh(cls, mesh, clean=False): pass
|
||||
|
||||
|
||||
@interface
|
||||
class Boundary: pass
|
||||
|
||||
@@ -92,3 +92,34 @@ class Blender:
|
||||
return tool.Ifc.get_entity(bpy.context.active_object).is_a(ifc_class)
|
||||
return False
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def apply_bmesh(cls, mesh, bm):
|
||||
import bmesh
|
||||
|
||||
if bpy.context.object and bpy.context.object.mode == "EDIT":
|
||||
# better to be safe because otherwise mesh won't be updated
|
||||
# and you won't get any errors
|
||||
if not bm.is_wrapped or hash(bmesh.from_edit_mesh(mesh)) != hash(bm):
|
||||
raise Exception(f'{bm} is not edit mesh for {mesh}. '
|
||||
'For applying bmesh in edit mode bmesh should be acquired with `bmesh.from_edit_mesh(me)`.')
|
||||
bmesh.update_edit_mesh(mesh)
|
||||
else:
|
||||
bm.to_mesh(mesh)
|
||||
|
||||
bm.free()
|
||||
mesh.update()
|
||||
|
||||
@classmethod
|
||||
def get_bmesh_for_mesh(cls, mesh, clean=False):
|
||||
import bmesh
|
||||
|
||||
if bpy.context.object.mode == "EDIT":
|
||||
bm = bmesh.from_edit_mesh(mesh)
|
||||
if clean:
|
||||
bm.clear()
|
||||
else:
|
||||
bm = bmesh.new()
|
||||
if not clean:
|
||||
bm.from_mesh(mesh)
|
||||
return bm
|
||||
Reference in New Issue
Block a user