Refactor and improve parametric gizmo system

- You can now input values using the keyboard once you clicked on a gizmo
- Gizmos now support click > move > click  in addition to drag and drop (yay carpal tunnel !)
- Optimize snapping performance
- Consolidate door and window type in model.py
- You can now cycle through window and door types using the cycle gizmo
- Pen, validate, cancel, lock, and cycle gizmos are now billboards and follow view direction
- Draggable gizmos are now billboard 2D arrows instead of 3D cones
This commit is contained in:
Gorgious
2025-11-28 23:22:42 +01:00
parent 5a8f557e7f
commit ffe9f65b3c
8 changed files with 1073 additions and 333 deletions
+2
View File
@@ -207,7 +207,9 @@ classes = [
ui.BIM_PT_decorators_overlay, ui.BIM_PT_decorators_overlay,
ui.BIM_PT_snappping, ui.BIM_PT_snappping,
# Gizmos # Gizmos
gizmo.BIM_OT_gizmo_value_input,
gizmo.GizmoArrow, gizmo.GizmoArrow,
gizmo.GizmoArrow2D,
gizmo.GizmoCone, gizmo.GizmoCone,
gizmo.GizmoLock, gizmo.GizmoLock,
gizmo.GizmoArc, gizmo.GizmoArc,
File diff suppressed because it is too large Load Diff
@@ -176,6 +176,7 @@ classes = (
window.FinishEditingWindow, window.FinishEditingWindow,
window.EnableEditingWindow, window.EnableEditingWindow,
window.RemoveWindow, window.RemoveWindow,
window.CycleWindowType,
window.GizmoWindowEdition, window.GizmoWindowEdition,
door.BIM_OT_add_door, door.BIM_OT_add_door,
door.AddDoor, door.AddDoor,
@@ -184,6 +185,7 @@ classes = (
door.EnableEditingDoor, door.EnableEditingDoor,
door.RemoveDoor, door.RemoveDoor,
door.ToggleDoorSwing, door.ToggleDoorSwing,
door.CycleDoorType,
door.GizmoDoorEdition, door.GizmoDoorEdition,
railing.BIM_OT_add_railing, railing.BIM_OT_add_railing,
railing.CopyRailingParameters, railing.CopyRailingParameters,
+40 -2
View File
@@ -41,6 +41,7 @@ from mathutils import Vector, Matrix
import json import json
import collections import collections
import collections.abc import collections.abc
from typing import get_args
V_ = tool.Blender.V_ V_ = tool.Blender.V_
@@ -741,6 +742,38 @@ class ToggleDoorSwing(bpy.types.Operator, tool.Ifc.Operator):
return {"FINISHED"} return {"FINISHED"}
class CycleDoorType(bpy.types.Operator, tool.Ifc.Operator):
"""Cycle through available door types. Shift+click to cycle in reverse."""
bl_idname = "bim.cycle_door_type"
bl_label = "Cycle Door Type"
bl_options = {"REGISTER", "UNDO"}
reverse: bpy.props.BoolProperty(name="Reverse", default=False, options={"HIDDEN", "SKIP_SAVE"})
def invoke(self, context, event):
self.reverse = event.shift
return self.execute(context)
def _execute(self, context):
obj = tool.Blender.get_active_object()
if not obj:
return {"CANCELLED"}
element = tool.Ifc.get_entity(obj)
if not element or not tool.Blender.Modifier.is_door(element):
return {"CANCELLED"}
props = tool.Model.get_door_props(obj)
door_types = get_args(tool.Model.DoorType)
current_index = door_types.index(props.door_type) if props.door_type in door_types else 0
direction = -1 if self.reverse else 1
next_index = (current_index + direction) % len(door_types)
props.door_type = door_types[next_index]
return {"FINISHED"}
class GizmoDoorEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup): class GizmoDoorEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
bl_idname = "OBJECT_GGT_bim_door_edition" bl_idname = "OBJECT_GGT_bim_door_edition"
bl_label = "Door Editing Gizmo" bl_label = "Door Editing Gizmo"
@@ -751,6 +784,7 @@ class GizmoDoorEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
enable_editing_operator = "bim.enable_editing_door" enable_editing_operator = "bim.enable_editing_door"
finish_editing_operator = "bim.finish_editing_door" finish_editing_operator = "bim.finish_editing_door"
cancel_editing_operator = "bim.cancel_editing_door" cancel_editing_operator = "bim.cancel_editing_door"
cycle_type_operator = "bim.cycle_door_type"
gizmo_props = [ gizmo_props = [
GizmoPropConfig("overall_height", (0, 0, 1)), GizmoPropConfig("overall_height", (0, 0, 1)),
@@ -833,11 +867,15 @@ class GizmoDoorEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
return translation @ rotation return translation @ rotation
def get_gizmo_matrix_transom_offset(self, props): def get_gizmo_matrix_transom_offset(self, props):
translation = Matrix.Translation(V_(props.overall_width / 2, props.lining_offset, props.transom_offset)) # Position at the bottom of the transom (transom extends upward from offset)
translation = Matrix.Translation(
V_(props.overall_width / 2, props.lining_offset, props.transom_offset)
)
rotation = self.get_axis_rotation_matrix((0, 0, 1)) rotation = self.get_axis_rotation_matrix((0, 0, 1))
return translation @ rotation return translation @ rotation
def get_gizmo_matrix_transom_thickness(self, props): def get_gizmo_matrix_transom_thickness(self, props):
# Position at the top of the transom (offset + thickness)
translation = Matrix.Translation( translation = Matrix.Translation(
V_(props.overall_width / 2, props.lining_offset, props.transom_offset + props.transom_thickness) V_(props.overall_width / 2, props.lining_offset, props.transom_offset + props.transom_thickness)
) )
@@ -904,7 +942,7 @@ class GizmoDoorEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
mw = obj.matrix_world mw = obj.matrix_world
self.update_property_gizmos(mw, props) self.update_property_gizmos(mw, props)
self.update_swing_gizmos(mw, props) self.update_swing_gizmos(mw, props)
self.update_editing_gizmos(mw, props) self.update_editing_gizmos(context, mw, props)
def update_swing_gizmos(self, mw, props): def update_swing_gizmos(self, mw, props):
"""Update swing gizmo position and color based on editing state.""" """Update swing gizmo position and color based on editing state."""
+5 -30
View File
@@ -671,19 +671,6 @@ def window_type_prop_update(self, context):
update_window(self, context) update_window(self, context)
WindowType = Literal[
"SINGLE_PANEL",
"DOUBLE_PANEL_HORIZONTAL",
"DOUBLE_PANEL_VERTICAL",
"TRIPLE_PANEL_BOTTOM",
"TRIPLE_PANEL_TOP",
"TRIPLE_PANEL_LEFT",
"TRIPLE_PANEL_RIGHT",
"TRIPLE_PANEL_HORIZONTAL",
"TRIPLE_PANEL_VERTICAL",
]
# default prop values are in mm and converted later # default prop values are in mm and converted later
class BIMWindowProperties(PropertyGroup): class BIMWindowProperties(PropertyGroup):
non_si_units_props = ( non_si_units_props = (
@@ -712,7 +699,7 @@ class BIMWindowProperties(PropertyGroup):
is_editing: bpy.props.BoolProperty(default=False) is_editing: bpy.props.BoolProperty(default=False)
window_type: bpy.props.EnumProperty( window_type: bpy.props.EnumProperty(
name="Window Type", name="Window Type",
items=[(i, i, "") for i in get_args(WindowType)], items=[(i, i, "") for i in get_args(tool.Model.WindowType)],
default="SINGLE_PANEL", default="SINGLE_PANEL",
update=window_type_prop_update, update=window_type_prop_update,
) )
@@ -788,7 +775,7 @@ class BIMWindowProperties(PropertyGroup):
if TYPE_CHECKING: if TYPE_CHECKING:
is_editing: bool is_editing: bool
window_type: WindowType window_type: tool.Model.WindowType
overall_height: float overall_height: float
overall_width: float overall_width: float
lining_depth: float lining_depth: float
@@ -822,7 +809,7 @@ class BIMWindowProperties(PropertyGroup):
return tool.Model.convert_data_to_project_units(kwargs, ["window_type"]) return tool.Model.convert_data_to_project_units(kwargs, ["window_type"])
def get_lining_kwargs( def get_lining_kwargs(
self, window_type: Optional[WindowType] = None, convert_to_project_units: bool = False self, window_type: Optional[tool.Model.WindowType] = None, convert_to_project_units: bool = False
) -> dict[str, Any]: ) -> dict[str, Any]:
if not window_type: if not window_type:
window_type = self.window_type window_type = self.window_type
@@ -889,18 +876,6 @@ class BIMWindowProperties(PropertyGroup):
setattr(target_props, prop_name, prop_value) setattr(target_props, prop_name, prop_value)
DoorType = Literal[
"SINGLE_SWING_LEFT",
"SINGLE_SWING_RIGHT",
"DOUBLE_SWING_LEFT",
"DOUBLE_SWING_RIGHT",
"DOUBLE_DOOR_SINGLE_SWING",
"SLIDING_TO_LEFT",
"SLIDING_TO_RIGHT",
"DOUBLE_DOOR_SLIDING",
]
class BIMDoorProperties(PropertyGroup): class BIMDoorProperties(PropertyGroup):
non_si_units_props = ( non_si_units_props = (
"is_editing", "is_editing",
@@ -913,7 +888,7 @@ class BIMDoorProperties(PropertyGroup):
is_editing: bpy.props.BoolProperty(default=False) is_editing: bpy.props.BoolProperty(default=False)
door_type: bpy.props.EnumProperty( door_type: bpy.props.EnumProperty(
name="Door Operation Type", name="Door Operation Type",
items=tuple((i, i, "") for i in get_args(DoorType)), items=tuple((i, i, "") for i in get_args(tool.Model.DoorType)),
default="SINGLE_SWING_LEFT", default="SINGLE_SWING_LEFT",
update=update_door, update=update_door,
) )
@@ -1057,7 +1032,7 @@ class BIMDoorProperties(PropertyGroup):
if TYPE_CHECKING: if TYPE_CHECKING:
is_editing: bool is_editing: bool
door_type: DoorType door_type: tool.Model.DoorType
overall_height: float overall_height: float
overall_width: float overall_width: float
+105 -29
View File
@@ -34,6 +34,7 @@ from bmesh.types import BMVert
from bpy.types import Operator from bpy.types import Operator
from bpy.props import FloatProperty, IntProperty from bpy.props import FloatProperty, IntProperty
from bpy_extras.object_utils import AddObjectHelper, object_data_add from bpy_extras.object_utils import AddObjectHelper, object_data_add
from typing import get_args
def regenerate_stair_mesh(obj: bpy.types.Object) -> None: def regenerate_stair_mesh(obj: bpy.types.Object) -> None:
@@ -357,13 +358,10 @@ class CycleStairType(bpy.types.Operator):
return {"CANCELLED"} return {"CANCELLED"}
props = tool.Model.get_stair_props(obj) props = tool.Model.get_stair_props(obj)
types = ["CONCRETE", "WOOD/STEEL", "GENERIC"] stair_types = get_args(tool.Model.StairType)
try: current_idx = stair_types.index(props.stair_type) if props.stair_type in stair_types else 0
current_idx = types.index(props.stair_type)
except ValueError:
current_idx = 0
direction = -1 if self.reverse else 1 direction = -1 if self.reverse else 1
props.stair_type = types[(current_idx + direction) % len(types)] props.stair_type = stair_types[(current_idx + direction) % len(stair_types)]
return {"FINISHED"} return {"FINISHED"}
@@ -375,6 +373,13 @@ class GizmoStairEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
bl_region_type = "WINDOW" bl_region_type = "WINDOW"
bl_options = {"3D", "PERSISTENT"} bl_options = {"3D", "PERSISTENT"}
# Gizmo layout offsets (meters)
GIZMO_X_OFFSET = 0.5 # Horizontal offset from stair end
GIZMO_PLUS_X_OFFSET = 0.25 # Additional X offset for plus button
GIZMO_MINUS_X_OFFSET = 0.5 # Additional X offset for minus button
GIZMO_BUTTON_Z_OFFSET = 0.15 # Vertical offset for +/- buttons above lock
GIZMO_CYCLE_Z_OFFSET = 0.5 # Vertical offset for cycle gizmo above lock
enable_editing_operator = "bim.enable_editing_stair" enable_editing_operator = "bim.enable_editing_stair"
finish_editing_operator = "bim.finish_editing_stair" finish_editing_operator = "bim.finish_editing_stair"
cancel_editing_operator = "bim.cancel_editing_stair" cancel_editing_operator = "bim.cancel_editing_stair"
@@ -432,6 +437,13 @@ class GizmoStairEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
rotation = self.get_axis_rotation_matrix((0, 1, 0)) rotation = self.get_axis_rotation_matrix((0, 1, 0))
return translation @ rotation return translation @ rotation
def get_gizmo_matrix_width_top(self, props):
"""Position for the secondary width gizmo at the top of the stairs."""
total_run = self._get_stair_total_run(props)
translation = Matrix.Translation(Vector((total_run, props.width, props.height)))
rotation = self.get_axis_rotation_matrix((0, 1, 0))
return translation @ rotation
def get_gizmo_matrix_height(self, props): def get_gizmo_matrix_height(self, props):
total_run = self._get_stair_total_run(props) total_run = self._get_stair_total_run(props)
translation = Matrix.Translation(Vector((total_run, props.width / 2, props.height))) translation = Matrix.Translation(Vector((total_run, props.width / 2, props.height)))
@@ -490,6 +502,41 @@ class GizmoStairEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
prefs = tool.Blender.get_addon_preferences() prefs = tool.Blender.get_addon_preferences()
highlight_color = prefs.decorator_color_selected[:3] highlight_color = prefs.decorator_color_selected[:3]
# Secondary width gizmo at the top of the stairs (linked to same width property)
self.gizmo_width_top = self.gizmos.new("BIM_GT_gizmo_arrow_2d")
def make_width_get():
def move_get():
obj = bpy.context.active_object
if not obj:
return 0.0
props = self.get_props(obj)
return props.width
return move_get
def make_width_set():
def move_set(value):
obj = bpy.context.active_object
if not obj:
return
props = self.get_props(obj)
props.width = max(0.0, value)
return move_set
self.gizmo_width_top.move_get_cb = make_width_get()
self.gizmo_width_top.move_set_cb = make_width_set()
self.gizmo_width_top.axis = Vector((0, 1, 0))
self.gizmo_width_top.local_axis = Vector((0, 1, 0))
self.gizmo_width_top.invert_delta = False
self.gizmo_width_top.delta_scale = 1.0
self.gizmo_width_top.prop_name = "Width"
self.gizmo_width_top.gizmo_group = self
self.gizmo_width_top.color = self.COLOR_GREEN
self.gizmo_width_top.color_highlight = highlight_color
self.gizmo_width_top.alpha = 0.99
self.gizmo_width_top.use_draw_modal = True
self.gizmo_width_top.use_draw_scale = True
# Total length lock gizmo # Total length lock gizmo
self.lock_gizmo = self.gizmos.new("VIEW3D_GT_lock") self.lock_gizmo = self.gizmos.new("VIEW3D_GT_lock")
self.lock_gizmo.use_draw_scale = False self.lock_gizmo.use_draw_scale = False
@@ -533,65 +580,81 @@ class GizmoStairEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
props = self.get_props(obj) props = self.get_props(obj)
mw = obj.matrix_world mw = obj.matrix_world
billboard_rot = gizmo.get_billboard_rotation(context)
self.update_property_gizmos(mw, props) self.update_property_gizmos(mw, props)
self.update_editing_gizmos(mw, props) self.update_editing_gizmos(context, mw, props)
self.update_lock_gizmo(mw, props) self.update_width_top_gizmo(mw, props)
self.update_tread_count_gizmos(mw, props) self.update_lock_gizmo(mw, props, billboard_rot)
self.update_cycle_gizmo(mw, props) self.update_tread_count_gizmos(mw, props, billboard_rot)
self.update_cycle_gizmo(mw, props, billboard_rot)
def update_lock_gizmo(self, mw, props): def update_width_top_gizmo(self, mw, props):
"""Update lock gizmo position and visibility.""" """Update the secondary width gizmo at the top of the stairs."""
gizmo_prefs = self.get_gizmo_prefs()
# Use same visibility as the main width gizmo
self.gizmo_width_top.hide = not props.is_editing or not getattr(gizmo_prefs, "width", True)
if self.gizmo_width_top.hide:
return
self.gizmo_width_top.matrix_basis = mw @ self.get_gizmo_matrix_width_top(props)
self.gizmo_width_top.matrix_offset = Matrix.Scale(self.ARROW_SCALE, 4)
def update_lock_gizmo(self, mw, props, billboard_rot):
gizmo_prefs = self.get_gizmo_prefs() gizmo_prefs = self.get_gizmo_prefs()
self.lock_gizmo.hide = not props.is_editing or not gizmo_prefs.lock self.lock_gizmo.hide = not props.is_editing or not gizmo_prefs.lock
if self.lock_gizmo.hide: if self.lock_gizmo.hide:
return return
# Update color based on lock state: red when locked, green when unlocked
self.lock_gizmo.color = self.COLOR_RED if props.total_length_lock else self.COLOR_GREEN self.lock_gizmo.color = self.COLOR_RED if props.total_length_lock else self.COLOR_GREEN
total_run = self._get_stair_total_run(props) total_run = self._get_stair_total_run(props)
lock_x = total_run + props.tread_run + 0.5 lock_x = total_run + props.tread_run + self.GIZMO_X_OFFSET
local_transform = ( local_transform = (
Matrix.Translation(Vector((lock_x, props.width / 2, props.height))) Matrix.Translation(Vector((lock_x, props.width / 2, props.height)))
@ Matrix.Rotation(math.radians(90), 4, (1.0, 0.0, 0.0)) @ billboard_rot
@ Matrix.Scale(0.2, 4) @ Matrix.Scale(0.2, 4)
) )
self.lock_gizmo.matrix_basis = mw @ local_transform self.lock_gizmo.matrix_basis = mw @ local_transform
def update_tread_count_gizmos(self, mw, props): def update_tread_count_gizmos(self, mw, props, billboard_rot):
"""Update plus and minus gizmos for tread count adjustment."""
gizmo_prefs = self.get_gizmo_prefs() gizmo_prefs = self.get_gizmo_prefs()
# Plus gizmo - always visible when editing (if preference enabled)
self.plus_gizmo.hide = not props.is_editing or not gizmo_prefs.plus self.plus_gizmo.hide = not props.is_editing or not gizmo_prefs.plus
# Minus gizmo - hidden when number_of_treads is 1 (if preference enabled)
self.minus_gizmo.hide = not props.is_editing or props.number_of_treads <= 1 or not gizmo_prefs.minus self.minus_gizmo.hide = not props.is_editing or props.number_of_treads <= 1 or not gizmo_prefs.minus
if self.plus_gizmo.hide and self.minus_gizmo.hide: if self.plus_gizmo.hide and self.minus_gizmo.hide:
return return
total_run = self._get_stair_total_run(props) total_run = self._get_stair_total_run(props)
base_x = total_run + props.tread_run + 0.5 base_x = total_run + props.tread_run + self.GIZMO_X_OFFSET
if not self.plus_gizmo.hide: if not self.plus_gizmo.hide:
plus_local_transform = ( plus_local_transform = (
Matrix.Translation(Vector((base_x + 0.25, props.width / 2, props.height + 0.15))) Matrix.Translation(Vector((
@ Matrix.Rotation(math.radians(90), 4, (1.0, 0.0, 0.0)) base_x + self.GIZMO_PLUS_X_OFFSET,
props.width / 2,
props.height + self.GIZMO_BUTTON_Z_OFFSET
)))
@ billboard_rot
@ Matrix.Scale(0.2, 4) @ Matrix.Scale(0.2, 4)
) )
self.plus_gizmo.matrix_basis = mw @ plus_local_transform self.plus_gizmo.matrix_basis = mw @ plus_local_transform
if not self.minus_gizmo.hide: if not self.minus_gizmo.hide:
minus_local_transform = ( minus_local_transform = (
Matrix.Translation(Vector((base_x + 0.5, props.width / 2, props.height + 0.15))) Matrix.Translation(Vector((
@ Matrix.Rotation(math.radians(90), 4, (1.0, 0.0, 0.0)) base_x + self.GIZMO_MINUS_X_OFFSET,
props.width / 2,
props.height + self.GIZMO_BUTTON_Z_OFFSET
)))
@ billboard_rot
@ Matrix.Scale(0.2, 4) @ Matrix.Scale(0.2, 4)
) )
self.minus_gizmo.matrix_basis = mw @ minus_local_transform self.minus_gizmo.matrix_basis = mw @ minus_local_transform
def update_cycle_gizmo(self, mw, props): def update_cycle_gizmo(self, mw, props, billboard_rot):
"""Update cycle gizmo position - 0.5m above lock gizmo."""
gizmo_prefs = self.get_gizmo_prefs() gizmo_prefs = self.get_gizmo_prefs()
self.cycle_gizmo.hide = not props.is_editing or not gizmo_prefs.cycle self.cycle_gizmo.hide = not props.is_editing or not gizmo_prefs.cycle
@@ -599,10 +662,23 @@ class GizmoStairEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
return return
total_run = self._get_stair_total_run(props) total_run = self._get_stair_total_run(props)
cycle_x = total_run + props.tread_run + 0.5 # Same X as lock cycle_x = total_run + props.tread_run + self.GIZMO_X_OFFSET
local_transform = ( local_transform = (
Matrix.Translation(Vector((cycle_x, props.width / 2, props.height + 0.5))) # +0.5m above lock Matrix.Translation(Vector((
@ Matrix.Rotation(math.radians(90), 4, (1.0, 0.0, 0.0)) cycle_x,
props.width / 2,
props.height + self.GIZMO_CYCLE_Z_OFFSET
)))
@ billboard_rot
@ Matrix.Scale(0.2, 4) @ Matrix.Scale(0.2, 4)
) )
self.cycle_gizmo.matrix_basis = mw @ local_transform self.cycle_gizmo.matrix_basis = mw @ local_transform
def draw_prepare(self, context):
"""Called before drawing - updates gizmos to face camera."""
# Call base class implementation
super().draw_prepare(context)
# Also update the secondary width gizmo to face camera
if hasattr(self, "gizmo_width_top") and not self.gizmo_width_top.hide:
self.gizmo_width_top.draw_prepare(context)
+40 -5
View File
@@ -39,6 +39,7 @@ import ifcopenshell.util.shape_builder
import ifcopenshell.util.unit import ifcopenshell.util.unit
from bmesh.types import BMVert from bmesh.types import BMVert
from mathutils import Vector, Matrix from mathutils import Vector, Matrix
from typing import get_args
V_ = tool.Blender.V_ V_ = tool.Blender.V_
@@ -579,6 +580,31 @@ class RemoveWindow(bpy.types.Operator, tool.Ifc.Operator):
return {"FINISHED"} return {"FINISHED"}
class CycleWindowType(bpy.types.Operator, tool.Ifc.Operator):
"""Cycle through available window types."""
bl_idname = "bim.cycle_window_type"
bl_label = "Cycle Window Type"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
obj = tool.Blender.get_active_object()
if not obj:
return {"CANCELLED"}
element = tool.Ifc.get_entity(obj)
if not element or not tool.Blender.Modifier.is_window(element):
return {"CANCELLED"}
props = tool.Model.get_window_props(obj)
window_types = list(get_args(tool.Model.WindowType))
current_index = window_types.index(props.window_type) if props.window_type in window_types else 0
next_index = (current_index + 1) % len(window_types)
props.window_type = window_types[next_index]
return {"FINISHED"}
class GizmoWindowEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup): class GizmoWindowEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
bl_idname = "OBJECT_GGT_bim_window_edition" bl_idname = "OBJECT_GGT_bim_window_edition"
bl_label = "Window Editing Gizmo" bl_label = "Window Editing Gizmo"
@@ -589,6 +615,7 @@ class GizmoWindowEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
enable_editing_operator = "bim.enable_editing_window" enable_editing_operator = "bim.enable_editing_window"
finish_editing_operator = "bim.finish_editing_window" finish_editing_operator = "bim.finish_editing_window"
cancel_editing_operator = "bim.cancel_editing_window" cancel_editing_operator = "bim.cancel_editing_window"
cycle_type_operator = "bim.cycle_window_type"
gizmo_props = [ gizmo_props = [
GizmoPropConfig("overall_height", (0, 0, 1)), GizmoPropConfig("overall_height", (0, 0, 1)),
@@ -597,10 +624,10 @@ class GizmoWindowEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
GizmoPropConfig("lining_thickness", (1, 0, 0)), GizmoPropConfig("lining_thickness", (1, 0, 0)),
GizmoPropConfig("lining_to_panel_offset_x", (1, 0, 0)), GizmoPropConfig("lining_to_panel_offset_x", (1, 0, 0)),
GizmoPropConfig("lining_to_panel_offset_y", (0, 1, 0)), GizmoPropConfig("lining_to_panel_offset_y", (0, 1, 0)),
GizmoPropConfig("mullion_thickness", (1, 0, 0)), GizmoPropConfig("mullion_thickness", (1, 0, 0), delta_scale=2.0),
GizmoPropConfig("first_mullion_offset", (1, 0, 0)), GizmoPropConfig("first_mullion_offset", (1, 0, 0)),
GizmoPropConfig("second_mullion_offset", (1, 0, 0)), GizmoPropConfig("second_mullion_offset", (1, 0, 0)),
GizmoPropConfig("transom_thickness", (0, 0, 1)), GizmoPropConfig("transom_thickness", (0, 0, 1), delta_scale=2.0),
GizmoPropConfig("first_transom_offset", (0, 0, 1)), GizmoPropConfig("first_transom_offset", (0, 0, 1)),
GizmoPropConfig("second_transom_offset", (0, 0, 1)), GizmoPropConfig("second_transom_offset", (0, 0, 1)),
] ]
@@ -690,6 +717,7 @@ class GizmoWindowEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
return translation @ rotation return translation @ rotation
def get_gizmo_matrix_mullion_thickness(self, props): def get_gizmo_matrix_mullion_thickness(self, props):
# Position at the right edge of the mullion (offset + thickness/2) so gizmo movement matches property 1:1
translation = Matrix.Translation( translation = Matrix.Translation(
V_(props.first_mullion_offset + props.mullion_thickness / 2, props.lining_offset, props.overall_height / 2) V_(props.first_mullion_offset + props.mullion_thickness / 2, props.lining_offset, props.overall_height / 2)
) )
@@ -697,7 +725,10 @@ class GizmoWindowEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
return translation @ rotation return translation @ rotation
def get_gizmo_matrix_first_mullion_offset(self, props): def get_gizmo_matrix_first_mullion_offset(self, props):
translation = Matrix.Translation(V_(props.first_mullion_offset, props.lining_offset, props.overall_height / 2)) # Position at the left edge of the mullion (offset - thickness/2)
translation = Matrix.Translation(
V_(props.first_mullion_offset - props.mullion_thickness / 2, props.lining_offset, props.overall_height / 2)
)
rotation = self.get_axis_rotation_matrix((1, 0, 0)) rotation = self.get_axis_rotation_matrix((1, 0, 0))
return translation @ rotation return translation @ rotation
@@ -707,6 +738,7 @@ class GizmoWindowEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
return translation @ rotation return translation @ rotation
def get_gizmo_matrix_transom_thickness(self, props): def get_gizmo_matrix_transom_thickness(self, props):
# Position at the top edge of the transom (offset + thickness/2) so gizmo movement matches property 1:1
translation = Matrix.Translation( translation = Matrix.Translation(
V_(props.overall_width / 2, props.lining_offset, props.first_transom_offset + props.transom_thickness / 2) V_(props.overall_width / 2, props.lining_offset, props.first_transom_offset + props.transom_thickness / 2)
) )
@@ -714,7 +746,10 @@ class GizmoWindowEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
return translation @ rotation return translation @ rotation
def get_gizmo_matrix_first_transom_offset(self, props): def get_gizmo_matrix_first_transom_offset(self, props):
translation = Matrix.Translation(V_(props.overall_width / 2, props.lining_offset, props.first_transom_offset)) # Position at the bottom edge of the transom (offset - thickness/2)
translation = Matrix.Translation(
V_(props.overall_width / 2, props.lining_offset, props.first_transom_offset - props.transom_thickness / 2)
)
rotation = self.get_axis_rotation_matrix((0, 0, 1)) rotation = self.get_axis_rotation_matrix((0, 0, 1))
return translation @ rotation return translation @ rotation
@@ -768,4 +803,4 @@ class GizmoWindowEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
props = self.get_props(obj) props = self.get_props(obj)
mw = obj.matrix_world mw = obj.matrix_world
self.update_property_gizmos(mw, props) self.update_property_gizmos(mw, props)
self.update_editing_gizmos(mw, props) self.update_editing_gizmos(context, mw, props)
+23
View File
@@ -1401,6 +1401,29 @@ class Model(bonsai.core.tool.Model):
StairType = Literal["CONCRETE", "WOOD/STEEL", "GENERIC"] StairType = Literal["CONCRETE", "WOOD/STEEL", "GENERIC"]
DoorType = Literal[
"SINGLE_SWING_LEFT",
"SINGLE_SWING_RIGHT",
"DOUBLE_SWING_LEFT",
"DOUBLE_SWING_RIGHT",
"DOUBLE_DOOR_SINGLE_SWING",
"SLIDING_TO_LEFT",
"SLIDING_TO_RIGHT",
"DOUBLE_DOOR_SLIDING",
]
WindowType = Literal[
"SINGLE_PANEL",
"DOUBLE_PANEL_HORIZONTAL",
"DOUBLE_PANEL_VERTICAL",
"TRIPLE_PANEL_BOTTOM",
"TRIPLE_PANEL_TOP",
"TRIPLE_PANEL_LEFT",
"TRIPLE_PANEL_RIGHT",
"TRIPLE_PANEL_HORIZONTAL",
"TRIPLE_PANEL_VERTICAL",
]
@classmethod @classmethod
def generate_stair_2d_profile( def generate_stair_2d_profile(
cls, cls,