UI fixes for railing and roof modifiers

Changes:
1) Added editing railing / roof path as hotkey to bim / cad tools.
2) Added profile decorator
3) Some switching modes and tools for convenience
4) Fixed nasty bug in tool.Blender.apply_bmesh - method wasn't updating current mesh if it was in edit mode.
This commit is contained in:
Andrej730
2023-03-08 16:56:29 +05:00
parent 4282014bfa
commit 4ad3d65628
8 changed files with 142 additions and 13 deletions
@@ -21,7 +21,7 @@ import bpy
import blenderbim.tool as tool import blenderbim.tool as tool
import blenderbim.bim.module.type.prop as type_prop import blenderbim.bim.module.type.prop as type_prop
from bpy.types import WorkSpaceTool from bpy.types import WorkSpaceTool
from blenderbim.bim.module.model.data import AuthoringData from blenderbim.bim.module.model.data import AuthoringData, RailingData, RoofData
class CadTool(WorkSpaceTool): class CadTool(WorkSpaceTool):
@@ -102,6 +102,7 @@ class CadTool(WorkSpaceTool):
row.label(text="", icon="EVENT_SHIFT") row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_X") row.label(text="", icon="EVENT_X")
row.operator("bim.reset_vertex", text="Reset Vertex") row.operator("bim.reset_vertex", text="Reset Vertex")
elif hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.subshape_type == "AXIS": elif hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.subshape_type == "AXIS":
row = layout.row(align=True) row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT") row.label(text="", icon="EVENT_SHIFT")
@@ -128,7 +129,30 @@ class CadTool(WorkSpaceTool):
row.label(text="", icon="EVENT_SHIFT") row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_O") row.label(text="", icon="EVENT_O")
row.operator("bim.cad_hotkey", text="Offset").hotkey = "S_O" row.operator("bim.cad_hotkey", text="Offset").hotkey = "S_O"
else: else:
if (
(RailingData.is_loaded or not RailingData.load())
and RailingData.data["parameters"]
and context.active_object.BIMRailingProperties.is_editing_path
):
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_Q")
row.operator("bim.hotkey", text="Apply Railing Path").hotkey = "S_Q"
row.operator("bim.cancel_editing_railing_path", icon="CANCEL", text="")
elif (
(RoofData.is_loaded or not RoofData.load())
and RoofData.data["parameters"]
and context.active_object.BIMRoofProperties.is_editing_path
):
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_Q")
row.operator("bim.hotkey", text="Apply Roof Path").hotkey = "S_Q"
row.operator("bim.cancel_editing_roof_path", icon="CANCEL", text="")
row = layout.row(align=True) row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT") row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_E") row.label(text="", icon="EVENT_E")
@@ -219,6 +243,21 @@ class CadHotkey(bpy.types.Operator):
bpy.ops.bim.edit_extrusion_profile() bpy.ops.bim.edit_extrusion_profile()
elif bpy.context.active_object.data.BIMMeshProperties.subshape_type == "AXIS": elif bpy.context.active_object.data.BIMMeshProperties.subshape_type == "AXIS":
bpy.ops.bim.edit_extrusion_axis() bpy.ops.bim.edit_extrusion_axis()
elif (
(RailingData.is_loaded or not RailingData.load())
and RailingData.data["parameters"]
and bpy.context.active_object.BIMRailingProperties.is_editing_path
):
bpy.ops.bim.finish_editing_railing_path()
elif (
(RoofData.is_loaded or not RoofData.load())
and RoofData.data["parameters"]
and bpy.context.active_object.BIMRoofProperties.is_editing_path
):
bpy.ops.bim.finish_editing_roof_path()
else: else:
bpy.ops.bim.edit_arbitrary_profile() bpy.ops.bim.edit_arbitrary_profile()
@@ -118,6 +118,7 @@ def update_door_modifier_representation(context):
update_simple_openings(element, props.overall_width, props.overall_height) update_simple_openings(element, props.overall_width, props.overall_height)
# TODO: move it out to tools
def bm_sort_out_geom(geom_data): def bm_sort_out_geom(geom_data):
geom_dict = {"verts": [], "edges": [], "faces": []} geom_dict = {"verts": [], "edges": [], "faces": []}
@@ -30,6 +30,7 @@ from blenderbim.bim.helper import convert_property_group_from_si
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.model.door import bm_sort_out_geom from blenderbim.bim.module.model.door import bm_sort_out_geom
from blenderbim.bim.module.model.data import RailingData, refresh from blenderbim.bim.module.model.data import RailingData, refresh
from blenderbim.bim.module.model.decorator import ProfileDecorator
from mathutils import Vector, Matrix from mathutils import Vector, Matrix
from pprint import pprint from pprint import pprint
@@ -40,6 +41,18 @@ import json
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcRailingType.htm # https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcRailingType.htm
# TODO: move it out to tools
def blender_get_viewport_context():
"""Get viewport area context for context overriding.
It's a bit naive since it's just taking the first available `VIEW_3D` area
when in real life you can have a couple of those.
"""
area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
context_override = {"area": area}
return context_override
def bm_split_edge_at_offset(edge, offset): def bm_split_edge_at_offset(edge, offset):
v0, v1 = edge.verts v0, v1 = edge.verts
@@ -352,10 +365,14 @@ class EnableEditingRailingPath(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context): def _execute(self, context):
obj = context.active_object obj = context.active_object
props = obj.BIMRailingProperties props = obj.BIMRailingProperties
element = tool.Ifc.get_entity(obj)
props.is_editing_path = True props.is_editing_path = True
update_railing_modifier_bmesh(context) update_railing_modifier_bmesh(context)
if bpy.context.object.mode != "EDIT":
bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.wm.tool_set_by_id(blender_get_viewport_context(), name="bim.cad_tool")
ProfileDecorator.install(context)
return {"FINISHED"} return {"FINISHED"}
@@ -366,10 +383,14 @@ class CancelEditingRailingPath(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context): def _execute(self, context):
obj = context.active_object obj = context.active_object
element = tool.Ifc.get_entity(obj)
props = obj.BIMRailingProperties props = obj.BIMRailingProperties
update_railing_modifier_bmesh(context)
ProfileDecorator.uninstall()
props.is_editing_path = False props.is_editing_path = False
update_railing_modifier_bmesh(context)
if bpy.context.object.mode == "EDIT":
bpy.ops.object.mode_set(mode="OBJECT")
return {"FINISHED"} return {"FINISHED"}
@@ -386,11 +407,14 @@ class FinishEditingRailingPath(bpy.types.Operator, tool.Ifc.Operator):
railing_data = props.get_general_kwargs() railing_data = props.get_general_kwargs()
path_data = get_path_data(obj) path_data = get_path_data(obj)
railing_data["path_data"] = path_data railing_data["path_data"] = path_data
ProfileDecorator.uninstall()
props.is_editing_path = False props.is_editing_path = False
update_bbim_railing_pset(element, railing_data) update_bbim_railing_pset(element, railing_data)
refresh() # need RailingData to be updated before update_railing_modifier_bmesh refresh() # RailingData has to be updated before run update_railing_modifier_bmesh
update_railing_modifier_bmesh(context) update_railing_modifier_bmesh(context)
if bpy.context.object.mode == "EDIT":
bpy.ops.object.mode_set(mode="OBJECT")
return {"FINISHED"} return {"FINISHED"}
@@ -25,7 +25,9 @@ import blenderbim
import blenderbim.tool as tool import blenderbim.tool as tool
from blenderbim.bim.helper import convert_property_group_from_si from blenderbim.bim.helper import convert_property_group_from_si
from blenderbim.bim.module.model.door import bm_sort_out_geom from blenderbim.bim.module.model.door import bm_sort_out_geom
from blenderbim.bim.module.model.railing import blender_get_viewport_context
from blenderbim.bim.module.model.data import RoofData, refresh from blenderbim.bim.module.model.data import RoofData, refresh
from blenderbim.bim.module.model.decorator import ProfileDecorator
import json import json
from math import tan, radians from math import tan, radians
@@ -135,13 +137,15 @@ def update_roof_modifier_ifc_data(context):
obj = context.active_object obj = context.active_object
props = obj.BIMRoofProperties props = obj.BIMRoofProperties
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
ifc_file = tool.Ifc.get()
# type attributes # type attributes
element.PredefinedType = props.roof_type element.PredefinedType = props.roof_type
# occurences attributes # occurences attributes
# occurences = tool.Ifc.get_all_element_occurences(element) # occurences = tool.Ifc.get_all_element_occurences(element)
# TODO: add Qto_RoofBaseQuantities, need to calculate GrossArea, NetArea, ProjectedArea
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/Qto_RoofBaseQuantities.htm
def update_bbim_roof_pset(element, roof_data): def update_bbim_roof_pset(element, roof_data):
pset = tool.Pset.get_element_pset(element, "BBIM_Roof") pset = tool.Pset.get_element_pset(element, "BBIM_Roof")
@@ -378,10 +382,14 @@ class EnableEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context): def _execute(self, context):
obj = context.active_object obj = context.active_object
props = obj.BIMRoofProperties props = obj.BIMRoofProperties
element = tool.Ifc.get_entity(obj)
props.is_editing_path = True props.is_editing_path = True
update_roof_modifier_bmesh(context) update_roof_modifier_bmesh(context)
if bpy.context.object.mode != "EDIT":
bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.wm.tool_set_by_id(blender_get_viewport_context(), name="bim.cad_tool")
ProfileDecorator.install(context)
return {"FINISHED"} return {"FINISHED"}
@@ -392,10 +400,14 @@ class CancelEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context): def _execute(self, context):
obj = context.active_object obj = context.active_object
element = tool.Ifc.get_entity(obj)
props = obj.BIMRoofProperties props = obj.BIMRoofProperties
update_roof_modifier_bmesh(context)
ProfileDecorator.uninstall()
props.is_editing_path = False props.is_editing_path = False
update_roof_modifier_bmesh(context)
if bpy.context.object.mode == "EDIT":
bpy.ops.object.mode_set(mode="OBJECT")
return {"FINISHED"} return {"FINISHED"}
@@ -412,11 +424,14 @@ class FinishEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator):
roof_data = props.get_general_kwargs() roof_data = props.get_general_kwargs()
path_data = get_path_data(obj) path_data = get_path_data(obj)
roof_data["path_data"] = path_data roof_data["path_data"] = path_data
ProfileDecorator.uninstall()
props.is_editing_path = False props.is_editing_path = False
update_bbim_roof_pset(element, roof_data) update_bbim_roof_pset(element, roof_data)
refresh() # need RoofData to be updated before update_roof_modifier_bmesh refresh() # RoofData has to be updated before run update_roof_modifier_bmesh
update_roof_modifier_bmesh(context) update_roof_modifier_bmesh(context)
if bpy.context.object.mode == "EDIT":
bpy.ops.object.mode_set(mode="OBJECT")
return {"FINISHED"} return {"FINISHED"}
@@ -612,6 +612,7 @@ class EnableEditingExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
bpy.ops.object.mode_set(mode="EDIT") bpy.ops.object.mode_set(mode="EDIT")
ProfileDecorator.install(context) ProfileDecorator.install(context)
# TODO: test it from properties panel?
if not bpy.app.background: if not bpy.app.background:
bpy.ops.wm.tool_set_by_id(name="bim.cad_tool") bpy.ops.wm.tool_set_by_id(name="bim.cad_tool")
return {"FINISHED"} return {"FINISHED"}
@@ -577,7 +577,7 @@ class BIM_PT_railing(bpy.types.Panel):
update_railing_modifier_bmesh(context) update_railing_modifier_bmesh(context)
elif props.is_editing_path: elif props.is_editing_path:
row.operator("bim.finish_editing_railing_path", icon="CHECKMARK", text="Finish editing path") row.operator("bim.finish_editing_railing_path", icon="CHECKMARK", text="")
row.operator("bim.cancel_editing_railing_path", icon="CANCEL", text="") row.operator("bim.cancel_editing_railing_path", icon="CANCEL", text="")
else: else:
@@ -636,7 +636,7 @@ class BIM_PT_roof(bpy.types.Panel):
update_roof_modifier_bmesh(context) update_roof_modifier_bmesh(context)
elif props.is_editing_path: elif props.is_editing_path:
row.operator("bim.finish_editing_roof_path", icon="CHECKMARK", text="Finish editing path") row.operator("bim.finish_editing_roof_path", icon="CHECKMARK", text="")
row.operator("bim.cancel_editing_roof_path", icon="CANCEL", text="") row.operator("bim.cancel_editing_roof_path", icon="CANCEL", text="")
else: else:
@@ -24,7 +24,7 @@ import blenderbim.tool as tool
import blenderbim.bim.module.type.prop as type_prop import blenderbim.bim.module.type.prop as type_prop
from blenderbim.bim.helper import prop_with_search, close_operator_panel from blenderbim.bim.helper import prop_with_search, close_operator_panel
from bpy.types import WorkSpaceTool from bpy.types import WorkSpaceTool
from blenderbim.bim.module.model.data import AuthoringData from blenderbim.bim.module.model.data import AuthoringData, RailingData, RoofData
from blenderbim.bim.module.model import prop from blenderbim.bim.module.model import prop
@@ -225,6 +225,7 @@ class BimToolUI:
row.operator("bim.join_wall", icon="X", text="").join_type = "" row.operator("bim.join_wall", icon="X", text="").join_type = ""
elif AuthoringData.data["active_material_usage"] == "LAYER3": elif AuthoringData.data["active_material_usage"] == "LAYER3":
# unnecessary check because BIM Tool is not available in EDIT mode?
if context.active_object.mode == "OBJECT": if context.active_object.mode == "OBJECT":
row = cls.layout.row(align=True) row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT") row.label(text="", icon="EVENT_SHIFT")
@@ -274,6 +275,7 @@ class BimToolUI:
row.label(text="", icon="EVENT_G") row.label(text="", icon="EVENT_G")
add_layout_hotkey_operator(row, "Regen", "S_G", bpy.ops.bim.recalculate_profile.__doc__) add_layout_hotkey_operator(row, "Regen", "S_G", bpy.ops.bim.recalculate_profile.__doc__)
row.operator("bim.extend_profile", icon="X", text="").join_type = "" row.operator("bim.extend_profile", icon="X", text="").join_type = ""
elif AuthoringData.data["active_class"] in ( elif AuthoringData.data["active_class"] in (
"IfcWindow", "IfcWindow",
"IfcWindowStandardCase", "IfcWindowStandardCase",
@@ -295,11 +297,13 @@ class BimToolUI:
row.label(text="", icon="EVENT_SHIFT") row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_F") row.label(text="", icon="EVENT_F")
row.operator("bim.hotkey", text="Flip").hotkey = "S_F" row.operator("bim.hotkey", text="Flip").hotkey = "S_F"
elif AuthoringData.data["active_class"] in ("IfcSpace",): elif AuthoringData.data["active_class"] in ("IfcSpace",):
row = cls.layout.row(align=True) row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT") row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_G") row.label(text="", icon="EVENT_G")
add_layout_hotkey_operator(row, "Regen", "S_G", bpy.ops.bim.generate_space.__doc__) add_layout_hotkey_operator(row, "Regen", "S_G", bpy.ops.bim.generate_space.__doc__)
elif AuthoringData.data["active_class"] in ( elif AuthoringData.data["active_class"] in (
"IfcCableCarrierSegmentType", "IfcCableCarrierSegmentType",
"IfcCableSegmentType", "IfcCableSegmentType",
@@ -310,6 +314,26 @@ class BimToolUI:
row.label(text="", icon="EVENT_SHIFT") row.label(text="", icon="EVENT_SHIFT")
row.label(text="Extend", icon="EVENT_E") row.label(text="Extend", icon="EVENT_E")
elif (
(RailingData.is_loaded or not RailingData.load())
and RailingData.data["parameters"]
and not context.active_object.BIMRailingProperties.is_editing_path
):
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_E")
row.operator("bim.hotkey", text="Edit Railing Path").hotkey = "S_E"
elif (
(RoofData.is_loaded or not RoofData.load())
and RoofData.data["parameters"]
and not context.active_object.BIMRoofProperties.is_editing_path
):
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_E")
row.operator("bim.hotkey", text="Edit Roof Path").hotkey = "S_E"
row = cls.layout.row(align=True) row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT") row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_O") row.label(text="", icon="EVENT_O")
@@ -317,6 +341,7 @@ class BimToolUI:
row.operator("bim.add_opening", text="Apply Void") row.operator("bim.add_opening", text="Apply Void")
else: else:
row.operator("bim.add_potential_opening", text="Add Void") row.operator("bim.add_potential_opening", text="Add Void")
if AuthoringData.data["is_voidable_element"]: if AuthoringData.data["is_voidable_element"]:
if AuthoringData.data["has_visible_openings"]: if AuthoringData.data["has_visible_openings"]:
row.operator("bim.edit_openings", icon="CHECKMARK", text="") row.operator("bim.edit_openings", icon="CHECKMARK", text="")
@@ -462,6 +487,7 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
def hotkey_S_Q(self): def hotkey_S_Q(self):
if not bpy.context.selected_objects: if not bpy.context.selected_objects:
return return
bpy.ops.bim.calculate_all_quantities() bpy.ops.bim.calculate_all_quantities()
def hotkey_S_C(self): def hotkey_S_C(self):
@@ -499,27 +525,49 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
elif self.active_material_usage == "PROFILE": elif self.active_material_usage == "PROFILE":
# Extend PROFILE to cursor # Extend PROFILE to cursor
bpy.ops.bim.extend_profile(join_type="T") bpy.ops.bim.extend_profile(join_type="T")
elif self.active_material_usage == "LAYER2" and selected_usages.get("PROFILE", []): elif self.active_material_usage == "LAYER2" and selected_usages.get("PROFILE", []):
# Extend PROFILEs to LAYER2 # Extend PROFILEs to LAYER2
[o.select_set(False) for o in selected_usages.get("LAYER3", [])] [o.select_set(False) for o in selected_usages.get("LAYER3", [])]
[o.select_set(False) for o in selected_usages.get("LAYER2", []) if o != bpy.context.active_object] [o.select_set(False) for o in selected_usages.get("LAYER2", []) if o != bpy.context.active_object]
bpy.ops.bim.extend_profile(join_type="T") bpy.ops.bim.extend_profile(join_type="T")
elif self.active_material_usage == "LAYER3" and selected_usages.get("LAYER2", []): elif self.active_material_usage == "LAYER3" and selected_usages.get("LAYER2", []):
# Extend LAYER2s to LAYER3 # Extend LAYER2s to LAYER3
[o.select_set(False) for o in selected_usages.get("PROFILE", [])] [o.select_set(False) for o in selected_usages.get("PROFILE", [])]
[o.select_set(False) for o in selected_usages.get("LAYER3", []) if o != bpy.context.active_object] [o.select_set(False) for o in selected_usages.get("LAYER3", []) if o != bpy.context.active_object]
bpy.ops.bim.join_wall(join_type="T") bpy.ops.bim.join_wall(join_type="T")
elif self.active_material_usage == "LAYER2": elif self.active_material_usage == "LAYER2":
# Extend LAYER2s to LAYER2 # Extend LAYER2s to LAYER2
[o.select_set(False) for o in selected_usages.get("LAYER3", [])] [o.select_set(False) for o in selected_usages.get("LAYER3", [])]
[o.select_set(False) for o in selected_usages.get("PROFILE", [])] [o.select_set(False) for o in selected_usages.get("PROFILE", [])]
bpy.ops.bim.join_wall(join_type="T") bpy.ops.bim.join_wall(join_type="T")
elif self.active_material_usage == "PROFILE": elif self.active_material_usage == "PROFILE":
# Extend PROFILEs to PROFILE # Extend PROFILEs to PROFILE
[o.select_set(False) for o in selected_usages.get("LAYER3", [])] [o.select_set(False) for o in selected_usages.get("LAYER3", [])]
[o.select_set(False) for o in selected_usages.get("LAYER2", [])] [o.select_set(False) for o in selected_usages.get("LAYER2", [])]
bpy.ops.bim.extend_profile(join_type="T") bpy.ops.bim.extend_profile(join_type="T")
elif (
(RailingData.is_loaded or not RailingData.load())
and RailingData.data["parameters"]
and not bpy.context.active_object.BIMRailingProperties.is_editing_path
):
# undo the unselection done above because railing has no usage type 🙃
bpy.context.object.select_set(True)
bpy.ops.bim.enable_editing_railing_path()
elif (
(RoofData.is_loaded or not RoofData.load())
and RoofData.data["parameters"]
and not bpy.context.active_object.BIMRoofProperties.is_editing_path
):
# undo the unselection done above because roof has no usage type
bpy.context.object.select_set(True)
bpy.ops.bim.enable_editing_roof_path()
def hotkey_S_F(self): def hotkey_S_F(self):
if not bpy.context.selected_objects: if not bpy.context.selected_objects:
return return
@@ -106,6 +106,7 @@ class Blender:
"For applying bmesh in edit mode bmesh should be acquired with `bmesh.from_edit_mesh(me)`." "For applying bmesh in edit mode bmesh should be acquired with `bmesh.from_edit_mesh(me)`."
) )
bmesh.update_edit_mesh(mesh) bmesh.update_edit_mesh(mesh)
bpy.context.object.update_from_editmode()
else: else:
bm.to_mesh(mesh) bm.to_mesh(mesh)