This commit is contained in:
Andrej730
2025-12-02 16:07:54 +05:00
parent 0ee084b5d9
commit dfb8298ecf
11 changed files with 330 additions and 191 deletions
+52 -50
View File
@@ -1558,9 +1558,7 @@ def snap_to_mesh(
return _snap_manager.snap_to_mesh(location, context, active_obj, mouse_coords, include_active)
def build_snap_cache(
context: bpy.types.Context, active_obj: bpy.types.Object, include_active: bool = False
) -> None:
def build_snap_cache(context: bpy.types.Context, active_obj: bpy.types.Object, include_active: bool = False) -> None:
_snap_manager.build_snap_cache(context, active_obj, include_active)
@@ -2013,7 +2011,6 @@ class UglyDotGizmo(OffsetHandle, types.Gizmo):
self.draw_custom_shape(self.custom_shape, select_id=select_id)
class ExtrusionGuidesGizmo(CustomGizmo, types.Gizmo):
"""Extrusion guides
@@ -2047,7 +2044,6 @@ class ExtrusionGuidesGizmo(CustomGizmo, types.Gizmo):
self.matrix_offset.col[2][2] = depth # z-scaled
class ExtrusionWidget(types.GizmoGroup):
bl_idname = "bim.extrusion_widget"
bl_label = "Extrusion Gizmos"
@@ -2098,7 +2094,6 @@ class ExtrusionWidget(types.GizmoGroup):
gz.target_set_prop("depth", prop, "value")
gz.scale_value = scale_value
def refresh(self, context: bpy.types.Context) -> None:
"""updating gizmos"""
target = context.active_object
@@ -2146,10 +2141,12 @@ class ExtrusionWidget(types.GizmoGroup):
scale_value /= si_conversions["thou"]
return scale_value
# ============================================================================
# Core Gizmo Classes
# ============================================================================
class BIM_OT_gizmo_value_input(bpy.types.Operator):
"""Enter a numeric value for a gizmo property. Click or Enter to confirm, ESC to cancel."""
@@ -2462,10 +2459,7 @@ class GizmoMovable(bpy.types.Gizmo):
self.keyboard_input.reset()
should_invoke_keyboard = (
not cancel
and hasattr(self, "_has_dragged")
and not self._has_dragged
and self.move_set_cb is not None
not cancel and hasattr(self, "_has_dragged") and not self._has_dragged and self.move_set_cb is not None
)
if should_invoke_keyboard:
@@ -2528,7 +2522,7 @@ class GizmoMovable(bpy.types.Gizmo):
if not self._has_dragged and hasattr(self, "_start_mouse_pos"):
dx = current_coord[0] - self._start_mouse_pos[0]
dy = current_coord[1] - self._start_mouse_pos[1]
if (dx * dx + dy * dy) > (self.DRAG_THRESHOLD ** 2):
if (dx * dx + dy * dy) > (self.DRAG_THRESHOLD**2):
self._has_dragged = True
view_origin = region_2d_to_origin_3d(region, rv3d, current_coord)
view_direction = region_2d_to_vector_3d(region, rv3d, current_coord)
@@ -2576,9 +2570,7 @@ class GizmoMovable(bpy.types.Gizmo):
return {"RUNNING_MODAL"}
def _handle_keyboard_input(
self, context: bpy.types.Context, event: bpy.types.Event
) -> set[str] | None:
def _handle_keyboard_input(self, context: bpy.types.Context, event: bpy.types.Event) -> set[str] | None:
"""Handle keyboard numeric input."""
kb = self.keyboard_input
@@ -3266,10 +3258,16 @@ class GizmoArrow2D(GizmoMovable):
return (
# Shaft
(0, -w, 0), (shaft, -w, 0), (0, w, 0),
(0, w, 0), (shaft, -w, 0), (shaft, w, 0),
(0, -w, 0),
(shaft, -w, 0),
(0, w, 0),
(0, w, 0),
(shaft, -w, 0),
(shaft, w, 0),
# Head
(shaft, -hw, 0), (shaft + head, 0, 0), (shaft, hw, 0),
(shaft, -hw, 0),
(shaft + head, 0, 0),
(shaft, hw, 0),
)
def setup(self) -> None:
@@ -3434,14 +3432,30 @@ class GizmoDimension(GizmoMovable):
"""Generate a simple clickable bar shape (unit length along X)."""
hw = self.HIT_WIDTH / 2
return (
(0, -hw, -hw), (1, -hw, -hw), (0, hw, -hw),
(0, hw, -hw), (1, -hw, -hw), (1, hw, -hw),
(0, -hw, hw), (0, hw, hw), (1, -hw, hw),
(1, -hw, hw), (0, hw, hw), (1, hw, hw),
(0, -hw, -hw), (0, -hw, hw), (1, -hw, -hw),
(1, -hw, -hw), (0, -hw, hw), (1, -hw, hw),
(0, hw, -hw), (1, hw, -hw), (0, hw, hw),
(0, hw, hw), (1, hw, -hw), (1, hw, hw),
(0, -hw, -hw),
(1, -hw, -hw),
(0, hw, -hw),
(0, hw, -hw),
(1, -hw, -hw),
(1, hw, -hw),
(0, -hw, hw),
(0, hw, hw),
(1, -hw, hw),
(1, -hw, hw),
(0, hw, hw),
(1, hw, hw),
(0, -hw, -hw),
(0, -hw, hw),
(1, -hw, -hw),
(1, -hw, -hw),
(0, -hw, hw),
(1, -hw, hw),
(0, hw, -hw),
(1, hw, -hw),
(0, hw, hw),
(0, hw, hw),
(1, hw, -hw),
(1, hw, hw),
)
def setup(self) -> None:
@@ -3483,9 +3497,7 @@ class GizmoDimension(GizmoMovable):
display_value=getattr(self, "_display_value", self._dimension_length),
)
def _calculate_screen_endpoints(
self, context: bpy.types.Context
) -> tuple[Vector, Vector, Vector, float] | None:
def _calculate_screen_endpoints(self, context: bpy.types.Context) -> tuple[Vector, Vector, Vector, float] | None:
"""Calculate screen-space endpoints and direction for the dimension line.
Returns:
@@ -3726,7 +3738,7 @@ class GizmoDimension(GizmoMovable):
if not self._has_dragged and hasattr(self, "_start_mouse_pos"):
dx = current_coord[0] - self._start_mouse_pos[0]
dy = current_coord[1] - self._start_mouse_pos[1]
if (dx * dx + dy * dy) > (self.DRAG_THRESHOLD ** 2):
if (dx * dx + dy * dy) > (self.DRAG_THRESHOLD**2):
self._has_dragged = True
view_origin = region_2d_to_origin_3d(region, rv3d, current_coord)
view_direction = region_2d_to_vector_3d(region, rv3d, current_coord)
@@ -3815,10 +3827,7 @@ class GizmoDimension(GizmoMovable):
self.keyboard_input.reset()
should_invoke_keyboard = (
not cancel
and hasattr(self, "_has_dragged")
and not self._has_dragged
and self.move_set_cb is not None
not cancel and hasattr(self, "_has_dragged") and not self._has_dragged and self.move_set_cb is not None
)
if should_invoke_keyboard:
@@ -3989,7 +3998,7 @@ class BaseParametricGizmoGroup:
GIZMO_CLAMP_MAX = 10000.0 # Maximum value for dimension clamping (meters)
# Pre-computed flip matrix for negative value handling (180° rotation around Z)
FLIP_MATRIX = Matrix.Rotation(math.pi, 4, 'Z')
FLIP_MATRIX = Matrix.Rotation(math.pi, 4, "Z")
# === Icon Gizmo Layout (meters) ===
# Icons are positioned in a horizontal row above the element:
@@ -4073,9 +4082,7 @@ class BaseParametricGizmoGroup:
from_neg_y, from_neg_x = self.get_local_view_direction(context, world_matrix)
return ViewDirection(from_negative_y=from_neg_y, from_negative_x=from_neg_x)
def update_gizmo_visibility(
self, gizmo: bpy.types.Gizmo, is_editing: bool, pref_enabled: bool
) -> bool:
def update_gizmo_visibility(self, gizmo: bpy.types.Gizmo, is_editing: bool, pref_enabled: bool) -> bool:
"""Update gizmo visibility based on modal state, editing state, and preference.
Consolidates the common pattern:
@@ -4154,9 +4161,7 @@ class BaseParametricGizmoGroup:
"""
return Matrix.Translation(translation) @ self.get_axis_rotation_matrix(axis)
def get_lining_y_position_for_view(
self, props, viewing_from_negative_y: bool, use_offset: bool = True
) -> float:
def get_lining_y_position_for_view(self, props, viewing_from_negative_y: bool, use_offset: bool = True) -> float:
"""Get Y position for lining-based elements (doors, windows) based on view direction.
For elements with lining_offset property, this calculates the Y position
@@ -4207,9 +4212,7 @@ class BaseParametricGizmoGroup:
Override in subclass if different positioning is needed.
"""
width = getattr(props, "overall_width", 0)
return self.compose_gizmo_matrix(
Vector((width + self.GIZMO_OFFSET, 0, -self.GIZMO_OFFSET)), (0, 1, 0)
)
return self.compose_gizmo_matrix(Vector((width + self.GIZMO_OFFSET, 0, -self.GIZMO_OFFSET)), (0, 1, 0))
def get_casing_offset(self, props) -> float:
"""Get casing offset for view-dependent dimension positioning.
@@ -4219,9 +4222,7 @@ class BaseParametricGizmoGroup:
"""
return 0.0
def _update_view_dependent_dimensions(
self, context: bpy.types.Context, mw: Matrix, props
) -> None:
def _update_view_dependent_dimensions(self, context: bpy.types.Context, mw: Matrix, props) -> None:
"""Update overall_width, overall_height, and lining_offset based on view direction.
This base implementation handles the common pattern for door/window gizmos.
@@ -4358,9 +4359,7 @@ class BaseParametricGizmoGroup:
self.update_dimension_gizmos(mw, props)
self._refresh_element_specific(context, mw, props)
def _refresh_element_specific(
self, context: bpy.types.Context, mw: "Matrix", props # noqa: ARG002
) -> None:
def _refresh_element_specific(self, context: bpy.types.Context, mw: "Matrix", props) -> None: # noqa: ARG002
"""Override for element-specific refresh logic.
Called after update_editing_gizmos and update_dimension_gizmos.
@@ -4383,6 +4382,7 @@ class BaseParametricGizmoGroup:
if self.props_getter:
return getattr(tool.Model, self.props_getter)(obj)
raise NotImplementedError("Subclass must define props_getter or override get_props()")
@staticmethod
def get_addon_prefs():
"""Get addon preferences (cached accessor)."""
@@ -4803,7 +4803,9 @@ class BaseParametricGizmoGroup:
self.cycle_gizmo.hide = self.is_gizmo_hidden_by_modal(self.cycle_gizmo)
local_pos_cycle = Vector((self.ICON_VALIDATE_X + self.ICON_CYCLE_X, icon_y, icon_z))
world_pos_cycle = mw @ local_pos_cycle
self.cycle_gizmo.matrix_basis = Matrix.Translation(world_pos_cycle) @ billboard_rot @ Matrix.Scale(0.30, 4)
self.cycle_gizmo.matrix_basis = (
Matrix.Translation(world_pos_cycle) @ billboard_rot @ Matrix.Scale(0.30, 4)
)
else:
self.pen_gizmo.hide = self.is_gizmo_hidden_by_modal(self.pen_gizmo)
self.pen_gizmo.matrix_basis = icon_matrix_base
@@ -1007,7 +1007,7 @@ class CreateDrawing(bpy.types.Operator):
for polygon in closed_polygons.geoms:
# Less than 0.1mm2 is not worth styling on sheet
if polygon.area < .1:
if polygon.area < 0.1:
continue
centroid = polygon.centroid
centroid = centroid if polygon.contains(centroid) else polygon.representative_point()
@@ -3275,15 +3275,15 @@ class AssignSelectedObjectAsProduct(bpy.types.Operator):
element1 = tool.Ifc.get_entity(obj1)
element2 = tool.Ifc.get_entity(obj2)
assert element1 and element2
# Check if at least one object is an IfcAnnotation
is_annotation1 = element1.is_a("IfcAnnotation")
is_annotation2 = element2.is_a("IfcAnnotation")
if not (is_annotation1 or is_annotation2):
self.report({"ERROR"}, "At least one of the selected objects must be IfcAnnotation.")
return {"CANCELLED"}
# If both are annotations, use the currently active object as relating product
if is_annotation1 and is_annotation2:
active_obj = context.active_object
@@ -2409,14 +2409,14 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
props = tool.Geometry.get_geometry_props()
item = tool.Geometry.get_active_representation(obj)
assert item
# Fix vertex order for annotation items
rep_obj = props.representation_obj
if rep_obj:
element = tool.Ifc.get_entity(rep_obj)
if element and self._is_annotation_object(element):
tool.Geometry.ensure_annotation_vertex_order(obj)
if tool.Geometry.is_meshlike_item(item):
if tool.Geometry.is_geometric_data(obj.data) and (
item.is_a("IfcVertex") or item.is_a("IfcEdge") or obj.data.polygons
@@ -2560,12 +2560,12 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
"""Check if element is an annotation object that needs vertex order preservation."""
if not element.is_a("IfcAnnotation"):
return False
# Object types that have semantic vertex order (arrow direction, text position)
annotation_types_with_order = {
"TEXT_LEADER",
"DIMENSION",
"RADIUS",
"RADIUS",
"DIAMETER",
"ANGLE",
"FALL",
@@ -2578,7 +2578,7 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
"SECTION",
"ELEVATION",
}
return element.ObjectType in annotation_types_with_order
@@ -3408,11 +3408,27 @@ class ImportRepresentationItems(bpy.types.Operator, tool.Ifc.Operator):
tool.Root.reload_item_decorator()
element = tool.Ifc.get_entity(obj)
if element and element.is_a("IfcAnnotation") and element.ObjectType in {
"TEXT_LEADER", "DIMENSION", "RADIUS", "DIAMETER", "ANGLE",
"FALL", "SLOPE_ANGLE", "SLOPE_FRACTION", "SLOPE_PERCENT",
"STAIR_ARROW", "PLAN_LEVEL", "SECTION_LEVEL", "SECTION", "ELEVATION"
}:
if (
element
and element.is_a("IfcAnnotation")
and element.ObjectType
in {
"TEXT_LEADER",
"DIMENSION",
"RADIUS",
"DIAMETER",
"ANGLE",
"FALL",
"SLOPE_ANGLE",
"SLOPE_FRACTION",
"SLOPE_PERCENT",
"STAIR_ARROW",
"PLAN_LEVEL",
"SECTION_LEVEL",
"SECTION",
"ELEVATION",
}
):
for item_obj_data in props.item_objs:
item_obj = item_obj_data.obj
if item_obj and isinstance(item_obj.data, bpy.types.Mesh) and item_obj.data.vertices:
+47 -27
View File
@@ -779,89 +779,105 @@ class GizmoDoorEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
# matrix_position lambdas replace the get_dimension_matrix_* methods
dimension_gizmo_props = [
DimensionGizmoConfig(
attr_name="overall_width", axis=(1, 0, 0), min_value=0.01, text_offset_sign=-1,
attr_name="overall_width",
axis=(1, 0, 0),
min_value=0.01,
text_offset_sign=-1,
# Position set dynamically in _update_dimension_gizmo_positions based on view
),
DimensionGizmoConfig(
attr_name="overall_height", axis=(0, 0, 1), min_value=0.01, text_alignment="start",
attr_name="overall_height",
axis=(0, 0, 1),
min_value=0.01,
text_alignment="start",
# Position set dynamically in _update_dimension_gizmo_positions based on view
),
DimensionGizmoConfig(
attr_name="threshold_thickness", axis=(0, 0, 1),
attr_name="threshold_thickness",
axis=(0, 0, 1),
matrix_position=lambda p: V_(p.overall_width / 2, p.threshold_offset + p.threshold_depth, 0),
),
DimensionGizmoConfig(
attr_name="threshold_depth", axis=(0, 1, 0),
attr_name="threshold_depth",
axis=(0, 1, 0),
visibility_condition=lambda p: p.has_threshold_depth(),
matrix_position=lambda p: V_(p.overall_width / 2, p.threshold_offset, p.threshold_thickness),
),
DimensionGizmoConfig(
attr_name="threshold_offset", axis=(0, 1, 0),
attr_name="threshold_offset",
axis=(0, 1, 0),
matrix_position=lambda p: V_(p.overall_width / 2 - _G.GIZMO_STACK_OFFSET, 0, p.threshold_thickness),
),
DimensionGizmoConfig(
attr_name="lining_offset", axis=(0, 1, 0), min_value=-10.0,
attr_name="lining_offset",
axis=(0, 1, 0),
min_value=-10.0,
# Position set dynamically in _update_dimension_gizmo_positions based on view
),
DimensionGizmoConfig(
attr_name="lining_depth", axis=(0, 1, 0),
attr_name="lining_depth",
axis=(0, 1, 0),
matrix_position=lambda p: V_(p.overall_width, p.lining_offset, p.overall_height),
),
DimensionGizmoConfig(
attr_name="lining_thickness", axis=(-1, 0, 0),
attr_name="lining_thickness",
axis=(-1, 0, 0),
matrix_position=lambda p: V_(p.overall_width, p.lining_depth / 2, p.overall_height / 2),
),
DimensionGizmoConfig(
attr_name="transom_offset", axis=(0, 0, 1),
attr_name="transom_offset",
axis=(0, 0, 1),
visibility_condition=lambda p: p.has_transom(),
matrix_position=lambda p: V_(p.overall_width / 2, p.lining_offset, 0),
),
DimensionGizmoConfig(
attr_name="transom_thickness", axis=(0, 0, 1),
attr_name="transom_thickness",
axis=(0, 0, 1),
matrix_position=lambda p: V_(p.overall_width / 2, p.lining_offset, p.transom_offset),
),
DimensionGizmoConfig(
attr_name="casing_thickness", axis=(-1, 0, 0),
attr_name="casing_thickness",
axis=(-1, 0, 0),
visibility_condition=lambda p: p.has_casing(),
matrix_position=lambda p: V_(
p.lining_thickness,
p.lining_depth + p.lining_offset + p.casing_depth / 2,
p.overall_height / 2
p.lining_thickness, p.lining_depth + p.lining_offset + p.casing_depth / 2, p.overall_height / 2
),
),
DimensionGizmoConfig(
attr_name="casing_depth", axis=(0, 1, 0),
attr_name="casing_depth",
axis=(0, 1, 0),
visibility_condition=lambda p: p.has_casing_depth(),
matrix_position=lambda p: V_(
p.lining_thickness - p.casing_thickness,
p.lining_depth + p.lining_offset,
p.overall_height / 2
p.lining_thickness - p.casing_thickness, p.lining_depth + p.lining_offset, p.overall_height / 2
),
),
DimensionGizmoConfig(
attr_name="panel_depth", axis=(0, 1, 0),
attr_name="panel_depth",
axis=(0, 1, 0),
matrix_position=lambda p: V_(
p.lining_to_panel_offset_x + p.overall_width * p.panel_width_ratio / 2,
p.lining_offset + p.lining_to_panel_offset_y,
p.threshold_thickness + p.get_panel_center_z()
p.threshold_thickness + p.get_panel_center_z(),
),
),
DimensionGizmoConfig(
attr_name="frame_thickness", axis=(-1, 0, 0),
attr_name="frame_thickness",
axis=(-1, 0, 0),
visibility_condition=lambda p: p.has_transom(),
matrix_position=lambda p: V_(
p.overall_width,
p.lining_offset + p.lining_to_panel_offset_y + p.frame_depth / 2,
p.get_transom_window_center_z()
p.get_transom_window_center_z(),
),
),
DimensionGizmoConfig(
attr_name="frame_depth", axis=(0, 1, 0),
attr_name="frame_depth",
axis=(0, 1, 0),
visibility_condition=lambda p: p.has_transom(),
matrix_position=lambda p: V_(
p.overall_width - p.frame_thickness,
p.lining_offset + p.lining_to_panel_offset_y,
p.get_transom_window_center_z()
p.get_transom_window_center_z(),
),
),
]
@@ -895,12 +911,14 @@ class GizmoDoorEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
special_color = prefs.decorator_color_special[:3]
self.gizmo_door_type = self.create_arc_gizmo(
special_color, "bim.toggle_door_swing",
special_color,
"bim.toggle_door_swing",
prop_path="BIMDoorProperties.door_type",
flip_geometry=False,
)
self.gizmo_flip_arc = self.create_arc_gizmo(
inactive_color, "bim.toggle_door_swing",
inactive_color,
"bim.toggle_door_swing",
prop_path="BIMDoorProperties.door_type",
flip_geometry=True,
flip_local_axes="XY",
@@ -916,7 +934,9 @@ class GizmoDoorEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
"""Override to return casing_thickness when lining_offset is 0."""
return props.get_casing_offset()
def _update_dimension_gizmo_positions(self, context: bpy.types.Context, mw: Matrix, props: "BIMDoorProperties") -> None:
def _update_dimension_gizmo_positions(
self, context: bpy.types.Context, mw: Matrix, props: "BIMDoorProperties"
) -> None:
"""Update dimension gizmo positions based on camera view direction."""
self._update_view_dependent_dimensions(context, mw, props)
@@ -417,12 +417,14 @@ class RecalculateFill(bpy.types.Operator, tool.Ifc.Operator):
representation=representation,
)
#Refresh cut decorator
# Refresh cut decorator
DecoratorData.cut_cache.clear()
DecoratorData.fill_cache.clear()
DecoratorData.slice_cache.clear()
return {"FINISHED"}
class FlipFill(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.flip_fill"
bl_label = "Flip Fill"
+33 -9
View File
@@ -171,6 +171,7 @@ def _get_updater(module_name: str, func_name: str) -> Callable:
cache_key = f"{module_name}.{func_name}"
if cache_key not in _updater_cache:
import importlib
module = importlib.import_module(f"bonsai.bim.module.model.{module_name}")
_updater_cache[cache_key] = getattr(module, func_name)
return _updater_cache[cache_key]
@@ -476,12 +477,18 @@ class BIMStairProperties(PropertyGroup):
name="Lock Total Length",
description="Lock Total Length when changing number of treads or tread run",
)
tread_depth: bpy.props.FloatProperty(name="Tread Depth", default=0.25, min=0.01, subtype="DISTANCE", update=update_stair)
tread_depth: bpy.props.FloatProperty(
name="Tread Depth", default=0.25, min=0.01, subtype="DISTANCE", update=update_stair
)
tread_run: bpy.props.FloatProperty(
name="Tread Run", default=0.3, min=0.01, subtype="DISTANCE", update=update_tread_run
)
base_slab_depth: bpy.props.FloatProperty(name="Base Slab Depth", default=0.25, min=0, subtype="DISTANCE", update=update_stair)
top_slab_depth: bpy.props.FloatProperty(name="Top Slab Depth", default=0.25, min=0, subtype="DISTANCE", update=update_stair)
base_slab_depth: bpy.props.FloatProperty(
name="Base Slab Depth", default=0.25, min=0, subtype="DISTANCE", update=update_stair
)
top_slab_depth: bpy.props.FloatProperty(
name="Top Slab Depth", default=0.25, min=0, subtype="DISTANCE", update=update_stair
)
has_top_nib: bpy.props.BoolProperty(name="Has Top Nib", default=True, update=update_stair)
stair_type: bpy.props.EnumProperty(
name="Stair Type",
@@ -515,7 +522,12 @@ class BIMStairProperties(PropertyGroup):
update=validate_nosing_value,
)
nosing_depth: bpy.props.FloatProperty(
name="Nosing Depth", description="Depth of the tread's nosing", min=0, default=0, unit="LENGTH", update=update_stair
name="Nosing Depth",
description="Depth of the tread's nosing",
min=0,
default=0,
unit="LENGTH",
update=update_stair,
)
if TYPE_CHECKING:
@@ -1087,7 +1099,7 @@ class BIMWindowProperties(PropertyGroup):
value: New value (clamped to min 0.0)
"""
current = getattr(self, attr_name)
new_value = tuple(current[:panel_index]) + (max(0.0, value),) + tuple(current[panel_index + 1:])
new_value = tuple(current[:panel_index]) + (max(0.0, value),) + tuple(current[panel_index + 1 :])
setattr(self, attr_name, new_value)
@@ -1401,7 +1413,10 @@ class BIMRailingProperties(PropertyGroup):
is_editing_path: bpy.props.BoolProperty(default=False)
railing_type: bpy.props.EnumProperty(
name="Railing Type", items=[(i, i, "") for i in get_args(RailingType)], default="FRAMELESS_PANEL", update=update_railing
name="Railing Type",
items=[(i, i, "") for i in get_args(RailingType)],
default="FRAMELESS_PANEL",
update=update_railing,
)
height: bpy.props.FloatProperty(name="Height", default=1.0, subtype="DISTANCE", update=update_railing)
thickness: bpy.props.FloatProperty(name="Thickness", default=0.050, subtype="DISTANCE", update=update_railing)
@@ -1423,7 +1438,9 @@ class BIMRailingProperties(PropertyGroup):
subtype="DISTANCE",
update=update_railing,
)
railing_diameter: bpy.props.FloatProperty(name="Railing Diameter", default=0.050, subtype="DISTANCE", update=update_railing)
railing_diameter: bpy.props.FloatProperty(
name="Railing Diameter", default=0.050, subtype="DISTANCE", update=update_railing
)
clear_width: bpy.props.FloatProperty(
name="Clear Width",
default=0.040,
@@ -1529,10 +1546,17 @@ class BIMRoofProperties(PropertyGroup):
name="Roof Type", items=[(i, i, "") for i in get_args(RoofType)], default="HIP/GABLE ROOF", update=update_roof
)
generation_method: bpy.props.EnumProperty(
name="Roof Generation Method", items=[(i, i, "") for i in get_args(RoofGenerationMethod)], default="ANGLE", update=update_roof
name="Roof Generation Method",
items=[(i, i, "") for i in get_args(RoofGenerationMethod)],
default="ANGLE",
update=update_roof,
)
height: bpy.props.FloatProperty(
name="Height", default=1.0, description="Maximum height of the roof to be generated.", subtype="DISTANCE", update=update_roof
name="Height",
default=1.0,
description="Maximum height of the roof to be generated.",
subtype="DISTANCE",
update=update_roof,
)
angle: bpy.props.FloatProperty(
name="Slope Angle",
+62 -22
View File
@@ -488,11 +488,16 @@ class GizmoStairEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
matrix_position=lambda p: V_(0, -_G.GIZMO_OFFSET, -_G.GIZMO_OFFSET),
),
DimensionGizmoConfig(
attr_name="height", axis=(0, 0, 1), min_value=0.01, text_alignment="start",
attr_name="height",
axis=(0, 0, 1),
min_value=0.01,
text_alignment="start",
matrix_position=lambda p: V_(p.get_total_run() + _G.GIZMO_OFFSET, -_G.GIZMO_OFFSET, 0),
),
DimensionGizmoConfig(
attr_name="width", axis=(0, 1, 0), min_value=0.01,
attr_name="width",
axis=(0, 1, 0),
min_value=0.01,
matrix_position=lambda p: V_(_G.GIZMO_OFFSET, 0, -_G.GIZMO_OFFSET),
),
DimensionGizmoConfig(
@@ -503,7 +508,7 @@ class GizmoStairEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
matrix_position=lambda p: V_(
0 if p.custom_tread_lock else p.custom_first_last_tread_run[0],
0,
p.get_riser_height() if p.custom_tread_lock else p.get_riser_height() * 2
p.get_riser_height() if p.custom_tread_lock else p.get_riser_height() * 2,
),
),
DimensionGizmoConfig(
@@ -524,12 +529,11 @@ class GizmoStairEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
visibility_condition=lambda p: p.has_custom_treads(),
compute_value=_tread_run_accessors[1][0],
apply_value=_tread_run_accessors[1][1],
matrix_position=lambda p: V_(
p.get_total_run() - p.custom_first_last_tread_run[1], 0, p.height
),
matrix_position=lambda p: V_(p.get_total_run() - p.custom_first_last_tread_run[1], 0, p.height),
),
DimensionGizmoConfig(
attr_name="nosing_length", axis=(-1, 0, 0),
attr_name="nosing_length",
axis=(-1, 0, 0),
matrix_position=lambda p: V_(0, p.width / 2, p.get_riser_height()),
),
DimensionGizmoConfig(
@@ -578,12 +582,16 @@ class GizmoStairEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
def setup_element_specific_gizmos(self, context: bpy.types.Context) -> None:
"""Create stair-specific icon gizmos (lock, plus, minus)."""
self.lock_gizmo = self.create_icon_gizmo(
"VIEW3D_GT_lock", self.COLOR_BLUE, "bim.toggle_stair_property",
"VIEW3D_GT_lock",
self.COLOR_BLUE,
"bim.toggle_stair_property",
prop_path="BIMStairProperties.total_length_lock",
property_name="total_length_lock",
)
self.tread_lock_gizmo = self.create_icon_gizmo(
"VIEW3D_GT_lock", (1.0, 1.0, 1.0), "bim.toggle_stair_property",
"VIEW3D_GT_lock",
(1.0, 1.0, 1.0),
"bim.toggle_stair_property",
prop_path="BIMStairProperties.custom_tread_lock",
property_name="custom_tread_lock",
)
@@ -594,9 +602,7 @@ class GizmoStairEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
"VIEW3D_GT_minus", self.COLOR_RED, "bim.adjust_stair_treads", increment=-1
)
def _refresh_element_specific(
self, context: bpy.types.Context, mw: Matrix, props: "BIMStairProperties"
) -> None:
def _refresh_element_specific(self, context: bpy.types.Context, mw: Matrix, props: "BIMStairProperties") -> None:
"""Update stair-specific lock and tread count gizmos."""
billboard_rot = gizmo.get_billboard_rotation(context)
self.update_lock_gizmo(mw, props, billboard_rot)
@@ -637,7 +643,9 @@ class GizmoStairEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
self.minus_gizmo, props.is_editing and props.number_of_treads > 1, gizmo_prefs.minus
)
def _update_dimension_gizmo_positions(self, context: bpy.types.Context, mw: Matrix, props: "BIMStairProperties") -> None:
def _update_dimension_gizmo_positions(
self, context: bpy.types.Context, mw: Matrix, props: "BIMStairProperties"
) -> None:
"""Update dimension gizmo positions based on camera view direction."""
viewing_from_negative_y, viewing_from_negative_x = self.get_local_view_direction(context, mw)
billboard_rot = gizmo.get_billboard_rotation(context)
@@ -651,7 +659,12 @@ class GizmoStairEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
self._update_editing_icon_positions(mw, props, viewing_from_negative_y, billboard_rot)
def _update_overall_dimension_gizmos(
self, mw: Matrix, props: "BIMStairProperties", viewing_from_negative_y: bool, viewing_from_negative_x: bool, total_run: float
self,
mw: Matrix,
props: "BIMStairProperties",
viewing_from_negative_y: bool,
viewing_from_negative_x: bool,
total_run: float,
) -> None:
"""Update overall dimension gizmos (total_length, width, height)."""
y_pos_offset = self.get_y_position_for_view(props, viewing_from_negative_y, use_offset=True)
@@ -662,7 +675,12 @@ class GizmoStairEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
self.set_dimension_gizmo_position("height", mw, V_(total_run + self.GIZMO_OFFSET, y_pos_offset, 0), (0, 0, 1))
def _update_tread_dimension_gizmos(
self, mw: Matrix, props: "BIMStairProperties", viewing_from_negative_y: bool, total_run: float, riser_height: float
self,
mw: Matrix,
props: "BIMStairProperties",
viewing_from_negative_y: bool,
total_run: float,
riser_height: float,
) -> None:
"""Update tread-related dimension gizmos (tread_run, custom first/last tread)."""
y_pos = self.get_y_position_for_view(props, viewing_from_negative_y, use_offset=False)
@@ -689,18 +707,33 @@ class GizmoStairEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
self.set_dimension_gizmo_position("nosing_length", mw, V_(0, props.width / 2, riser_height), (-1, 0, 0))
self.set_dimension_gizmo_position("tread_depth", mw, V_(0, y_pos, riser_height), (0, 0, -1))
self.set_dimension_gizmo_position("riser_height", mw, V_(props.tread_run, y_pos, 0), (0, 0, 1))
self.set_dimension_gizmo_position("nosing_depth", mw, V_(-props.nosing_length, props.width / 2, riser_height), (0, 0, -1))
self.set_dimension_gizmo_position(
"nosing_depth", mw, V_(-props.nosing_length, props.width / 2, riser_height), (0, 0, -1)
)
def _update_lock_gizmo_position(
self, mw: Matrix, props: "BIMStairProperties", viewing_from_negative_y: bool, billboard_rot: Matrix, total_run: float
self,
mw: Matrix,
props: "BIMStairProperties",
viewing_from_negative_y: bool,
billboard_rot: Matrix,
total_run: float,
) -> None:
"""Update lock gizmo position based on Y view direction."""
y_pos = self.get_y_position_for_view(props, viewing_from_negative_y, use_offset=True)
self.set_icon_gizmo_position(
"lock_gizmo", mw, total_run + self.ICON_Z_OFFSET, y_pos, -self.GIZMO_OFFSET, billboard_rot, scale=self.EDITING_ICON_SCALE
"lock_gizmo",
mw,
total_run + self.ICON_Z_OFFSET,
y_pos,
-self.GIZMO_OFFSET,
billboard_rot,
scale=self.EDITING_ICON_SCALE,
)
def _update_editing_icon_positions(self, mw: Matrix, props: "BIMStairProperties", viewing_from_negative_y: bool, billboard_rot: Matrix) -> None:
def _update_editing_icon_positions(
self, mw: Matrix, props: "BIMStairProperties", viewing_from_negative_y: bool, billboard_rot: Matrix
) -> None:
"""Update editing icon positions, flipping Y based on viewing angle."""
if not props.is_editing:
return
@@ -710,10 +743,17 @@ class GizmoStairEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
self.set_icon_gizmo_position("validate_gizmo", mw, 0, y_pos, icon_z, billboard_rot)
self.set_icon_gizmo_position("cancel_gizmo", mw, self.ICON_CANCEL_X, y_pos, icon_z, billboard_rot)
self.set_icon_gizmo_position("cycle_gizmo", mw, self.ICON_CYCLE_X, y_pos, icon_z, billboard_rot, scale=self.ICON_CYCLE_SCALE)
self.set_icon_gizmo_position(
"tread_lock_gizmo", mw, self.ICON_TREAD_LOCK_X, y_pos,
icon_z - self.EDITING_ICON_SCALE / 2, billboard_rot, scale=self.EDITING_ICON_SCALE
"cycle_gizmo", mw, self.ICON_CYCLE_X, y_pos, icon_z, billboard_rot, scale=self.ICON_CYCLE_SCALE
)
self.set_icon_gizmo_position(
"tread_lock_gizmo",
mw,
self.ICON_TREAD_LOCK_X,
y_pos,
icon_z - self.EDITING_ICON_SCALE / 2,
billboard_rot,
scale=self.EDITING_ICON_SCALE,
)
self.set_icon_gizmo_position(
"plus_gizmo", mw, self.ICON_PLUS_X, y_pos, icon_z, billboard_rot, scale=self.ICON_PLUS_MINUS_SCALE
+56 -33
View File
@@ -605,9 +605,10 @@ class CycleWindowType(bpy.types.Operator, tool.Ifc.Operator, gizmo.CycleTypeMixi
# Frame accessor factory - creates callbacks that delegate to BIMWindowProperties methods
def _make_frame_accessors(
attr_name: str, panel_index: int
) -> tuple["collections.abc.Callable[[BIMWindowProperties], float]", "collections.abc.Callable[[BIMWindowProperties, float], None]"]:
def _make_frame_accessors(attr_name: str, panel_index: int) -> tuple[
"collections.abc.Callable[[BIMWindowProperties], float]",
"collections.abc.Callable[[BIMWindowProperties, float], None]",
]:
"""Create compute/apply callbacks for frame properties at a specific panel index.
Args:
@@ -624,9 +625,7 @@ def _make_frame_accessors(
_frame_accessors = {
(attr, idx): _make_frame_accessors(attr, idx)
for attr in ("frame_depth", "frame_thickness")
for idx in range(3)
(attr, idx): _make_frame_accessors(attr, idx) for attr in ("frame_depth", "frame_thickness") for idx in range(3)
}
@@ -645,112 +644,137 @@ class GizmoWindowEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
# matrix_position lambdas replace the get_dimension_matrix_* methods
dimension_gizmo_props = [
DimensionGizmoConfig(
attr_name="overall_width", axis=(1, 0, 0), min_value=0.01, text_offset_sign=-1,
attr_name="overall_width",
axis=(1, 0, 0),
min_value=0.01,
text_offset_sign=-1,
matrix_position=lambda p: V_(0, p.lining_offset - _G.GIZMO_OFFSET, -_G.GIZMO_OFFSET),
),
DimensionGizmoConfig(
attr_name="overall_height", axis=(0, 0, 1), min_value=0.01, text_alignment="start",
attr_name="overall_height",
axis=(0, 0, 1),
min_value=0.01,
text_alignment="start",
matrix_position=lambda p: V_(p.overall_width + _G.GIZMO_OFFSET, p.lining_offset - _G.GIZMO_OFFSET, 0),
),
DimensionGizmoConfig(
attr_name="lining_depth", axis=(0, 1, 0),
attr_name="lining_depth",
axis=(0, 1, 0),
matrix_position=lambda p: V_(p.overall_width / 2, p.lining_offset, p.overall_height),
),
DimensionGizmoConfig(
attr_name="lining_thickness", axis=(1, 0, 0),
attr_name="lining_thickness",
axis=(1, 0, 0),
matrix_position=lambda p: V_(0, p.lining_depth / 2 + p.lining_offset, p.overall_height / 2),
),
DimensionGizmoConfig(
attr_name="lining_to_panel_offset_x", axis=(1, 0, 0),
attr_name="lining_to_panel_offset_x",
axis=(1, 0, 0),
matrix_position=lambda p: V_(
0,
p.get_lining_to_panel_offset_y_full() + p.frame_depth[0] + p.lining_offset,
p.lining_to_panel_offset_x
p.lining_to_panel_offset_x,
),
),
DimensionGizmoConfig(
attr_name="lining_to_panel_offset_y", axis=(0, 1, 0), min_value=-10.0,
attr_name="lining_to_panel_offset_y",
axis=(0, 1, 0),
min_value=-10.0,
matrix_position=lambda p: V_(
p.overall_width - p.lining_to_panel_offset_x,
p.lining_depth + p.lining_offset,
p.lining_to_panel_offset_x
p.lining_to_panel_offset_x,
),
),
DimensionGizmoConfig(
attr_name="frame_depth", axis=(0, -1, 0),
attr_name="frame_depth",
axis=(0, -1, 0),
compute_value=_frame_accessors[("frame_depth", 0)][0],
apply_value=_frame_accessors[("frame_depth", 0)][1],
matrix_position=lambda p: p.get_frame_position(0, is_depth=True),
),
DimensionGizmoConfig(
attr_name="frame_thickness", axis=(1, 0, 0),
attr_name="frame_thickness",
axis=(1, 0, 0),
compute_value=_frame_accessors[("frame_thickness", 0)][0],
apply_value=_frame_accessors[("frame_thickness", 0)][1],
matrix_position=lambda p: p.get_frame_position(0, is_depth=False),
),
DimensionGizmoConfig(
attr_name="second_frame_depth", axis=(0, -1, 0),
attr_name="second_frame_depth",
axis=(0, -1, 0),
compute_value=_frame_accessors[("frame_depth", 1)][0],
apply_value=_frame_accessors[("frame_depth", 1)][1],
visibility_condition=lambda p: p.has_second_panel(),
matrix_position=lambda p: p.get_frame_position(1, is_depth=True),
),
DimensionGizmoConfig(
attr_name="second_frame_thickness", axis=(1, 0, 0),
attr_name="second_frame_thickness",
axis=(1, 0, 0),
compute_value=_frame_accessors[("frame_thickness", 1)][0],
apply_value=_frame_accessors[("frame_thickness", 1)][1],
visibility_condition=lambda p: p.has_second_panel(),
matrix_position=lambda p: p.get_frame_position(1, is_depth=False),
),
DimensionGizmoConfig(
attr_name="third_frame_depth", axis=(0, -1, 0),
attr_name="third_frame_depth",
axis=(0, -1, 0),
compute_value=_frame_accessors[("frame_depth", 2)][0],
apply_value=_frame_accessors[("frame_depth", 2)][1],
visibility_condition=lambda p: p.has_third_panel(),
matrix_position=lambda p: p.get_frame_position(2, is_depth=True),
),
DimensionGizmoConfig(
attr_name="third_frame_thickness", axis=(1, 0, 0),
attr_name="third_frame_thickness",
axis=(1, 0, 0),
compute_value=_frame_accessors[("frame_thickness", 2)][0],
apply_value=_frame_accessors[("frame_thickness", 2)][1],
visibility_condition=lambda p: p.has_third_panel(),
matrix_position=lambda p: p.get_frame_position(2, is_depth=False),
),
DimensionGizmoConfig(
attr_name="mullion_thickness", axis=(1, 0, 0), delta_scale=2.0,
attr_name="mullion_thickness",
axis=(1, 0, 0),
delta_scale=2.0,
visibility_condition=lambda p: p.has_mullion(),
matrix_position=lambda p: V_(
p.first_mullion_offset - p.mullion_thickness / 2,
p.lining_offset,
p.overall_height / 2 + 3 * _G.GIZMO_STACK_OFFSET
p.overall_height / 2 + 3 * _G.GIZMO_STACK_OFFSET,
),
),
DimensionGizmoConfig(
attr_name="first_mullion_offset", axis=(1, 0, 0),
attr_name="first_mullion_offset",
axis=(1, 0, 0),
visibility_condition=lambda p: p.has_mullion(),
matrix_position=lambda p: V_(0, p.lining_offset, p.overall_height / 2 + _G.GIZMO_STACK_OFFSET),
),
DimensionGizmoConfig(
attr_name="second_mullion_offset", axis=(1, 0, 0),
attr_name="second_mullion_offset",
axis=(1, 0, 0),
visibility_condition=lambda p: p.has_second_mullion(),
matrix_position=lambda p: V_(0, p.lining_offset, p.overall_height / 2 + 2 * _G.GIZMO_STACK_OFFSET),
),
DimensionGizmoConfig(
attr_name="transom_thickness", axis=(0, 0, 1), delta_scale=2.0,
attr_name="transom_thickness",
axis=(0, 0, 1),
delta_scale=2.0,
visibility_condition=lambda p: p.has_transom(),
matrix_position=lambda p: V_(
p.overall_width / 2 + 2 * _G.GIZMO_STACK_OFFSET,
p.lining_offset,
p.first_transom_offset - p.transom_thickness / 2
p.first_transom_offset - p.transom_thickness / 2,
),
),
DimensionGizmoConfig(
attr_name="first_transom_offset", axis=(0, 0, 1),
attr_name="first_transom_offset",
axis=(0, 0, 1),
visibility_condition=lambda p: p.has_transom(),
matrix_position=lambda p: V_(p.overall_width / 2, p.lining_offset, 0),
),
DimensionGizmoConfig(
attr_name="second_transom_offset", axis=(0, 0, 1),
attr_name="second_transom_offset",
axis=(0, 0, 1),
visibility_condition=lambda p: p.has_second_transom(),
matrix_position=lambda p: V_(p.overall_width / 2 + _G.GIZMO_STACK_OFFSET, p.lining_offset, 0),
),
@@ -772,17 +796,16 @@ class GizmoWindowEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
depending on lining_offset (which can be negative).
"""
furthest_positive_y = (
max(0, props.lining_offset)
+ props.lining_depth
+ props.lining_to_panel_offset_y
+ 2 * self.GIZMO_OFFSET
max(0, props.lining_offset) + props.lining_depth + props.lining_to_panel_offset_y + 2 * self.GIZMO_OFFSET
)
furthest_negative_y = abs(min(0, props.lining_offset)) + 2 * self.GIZMO_OFFSET
return (furthest_positive_y, furthest_negative_y)
# Window uses base class setup() and refresh() - no element-specific gizmos needed
def _update_dimension_gizmo_positions(self, context: bpy.types.Context, mw: Matrix, props: "BIMWindowProperties") -> None:
def _update_dimension_gizmo_positions(
self, context: bpy.types.Context, mw: Matrix, props: "BIMWindowProperties"
) -> None:
"""Update dimension gizmo positions based on camera view direction."""
# Window uses base implementation with default casing_offset=0
self._update_view_dependent_dimensions(context, mw, props)
@@ -673,6 +673,3 @@ class BIM_PT_purge(Panel):
row.prop(ifc_parameter, "name", text="")
row.prop(ifc_parameter, "value", text="")
row.operator("bim.update_parametric_representation", icon="FILE_REFRESH", text="").index = index
+15 -11
View File
@@ -58,7 +58,7 @@ class AssignType(bpy.types.Operator, tool.Ifc.Operator):
else:
related_objects = tool.Blender.get_selected_objects()
prefs = tool.Blender.get_addon_preferences()
# Get the active drawing's target view
active_target_view = None
drawing_props = context.scene.DocProperties
@@ -66,13 +66,13 @@ class AssignType(bpy.types.Operator, tool.Ifc.Operator):
active_drawing = tool.Ifc.get().by_id(drawing_props.active_drawing_id)
if active_drawing:
active_target_view = tool.Drawing.get_drawing_target_view(active_drawing)
for obj in related_objects:
element = tool.Ifc.get_entity(obj)
if not element or not element.is_a("IfcObject"):
continue
core.assign_type(tool.Ifc, tool.Type, element=element, type=relating_type)
# Switch to the drawing's target view if available
if active_target_view and element.Representation:
for rep in element.Representation.Representations:
@@ -84,7 +84,7 @@ class AssignType(bpy.types.Operator, tool.Ifc.Operator):
representation=rep,
)
break
if prefs.occurrence_name_style == "TYPE":
obj.name = tool.Model.generate_occurrence_name(relating_type, element.is_a())
@@ -363,17 +363,17 @@ class DuplicateType(bpy.types.Operator, tool.Ifc.Operator):
if obj.data:
new_obj.data = obj.data.copy()
new = bonsai.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj)
# Apply the name and description from the dialog
new.Name = self.name
if self.description:
new.Description = self.description
# Update the Blender object name to match the IFC element name
tool.Root.set_object_name(new_obj, new)
bpy.ops.bim.load_type_thumbnails()
# Assign selected objects to the new type if requested
if self.assign_selected_objects:
selected_objects = tool.Blender.get_selected_objects()
@@ -384,7 +384,7 @@ class DuplicateType(bpy.types.Operator, tool.Ifc.Operator):
core.assign_type(tool.Ifc, tool.Type, element=selected_element, type=new)
if prefs.occurrence_name_style == "TYPE":
selected_obj.name = tool.Model.generate_occurrence_name(new, selected_element.is_a())
if obj in context.selectable_objects:
tool.Blender.select_and_activate_single_object(context, new_obj)
else:
@@ -409,6 +409,10 @@ class DuplicateType(bpy.types.Operator, tool.Ifc.Operator):
self.layout.prop(self, "name")
self.layout.prop(self, "description")
selected_objects = tool.Blender.get_selected_objects()
ifc_objects = [obj for obj in selected_objects if tool.Ifc.get_entity(obj) and tool.Ifc.get_entity(obj).is_a("IfcObject")]
ifc_objects = [
obj for obj in selected_objects if tool.Ifc.get_entity(obj) and tool.Ifc.get_entity(obj).is_a("IfcObject")
]
if ifc_objects:
self.layout.prop(self, "assign_selected_objects", text=f"Assign {len(ifc_objects)} Selected Object(s) to New Type")
self.layout.prop(
self, "assign_selected_objects", text=f"Assign {len(ifc_objects)} Selected Object(s) to New Type"
)
+32 -21
View File
@@ -1907,9 +1907,20 @@ class Geometry(bonsai.core.tool.Geometry):
# ADD THIS AT THE END - Store initial vertex order for annotations
if rep_obj and (element := tool.Ifc.get_entity(rep_obj)):
if element.is_a("IfcAnnotation") and element.ObjectType in {
"TEXT_LEADER", "DIMENSION", "RADIUS", "DIAMETER", "ANGLE",
"FALL", "SLOPE_ANGLE", "SLOPE_FRACTION", "SLOPE_PERCENT",
"STAIR_ARROW", "PLAN_LEVEL", "SECTION_LEVEL", "SECTION", "ELEVATION"
"TEXT_LEADER",
"DIMENSION",
"RADIUS",
"DIAMETER",
"ANGLE",
"FALL",
"SLOPE_ANGLE",
"SLOPE_FRACTION",
"SLOPE_PERCENT",
"STAIR_ARROW",
"PLAN_LEVEL",
"SECTION_LEVEL",
"SECTION",
"ELEVATION",
}:
# Store the initial first vertex position
if isinstance(obj.data, bpy.types.Mesh) and obj.data.vertices:
@@ -2361,7 +2372,7 @@ class Geometry(bonsai.core.tool.Geometry):
mesh = obj.data
if not isinstance(mesh, bpy.types.Mesh):
return
# Get the original first vertex position from custom properties
if "bonsai_first_vert_co" in mesh:
original_first_co = Vector(mesh["bonsai_first_vert_co"])
@@ -2372,19 +2383,19 @@ class Geometry(bonsai.core.tool.Geometry):
mesh["bonsai_first_vert_co"] = original_first_co[:]
else:
return
bm = bmesh.new()
bm.from_mesh(mesh)
bm.verts.ensure_lookup_table()
bm.edges.ensure_lookup_table()
if len(bm.verts) == 0:
bm.free()
return
# Find endpoints (vertices with only one connected edge)
endpoints = [v for v in bm.verts if len(v.link_edges) == 1]
# Choose the endpoint closest to the original first vertex position
if len(endpoints) == 0:
# Closed loop - pick any vertex as start
@@ -2395,12 +2406,12 @@ class Geometry(bonsai.core.tool.Geometry):
else:
# Choose endpoint closest to where the original first vertex was
start_vert = min(endpoints, key=lambda v: (v.co - original_first_co).length)
# Build ordered vertex list by following edges
ordered_verts = [start_vert]
current_vert = start_vert
visited_edges = set()
while True:
# Find next unvisited edge
next_edge = None
@@ -2408,37 +2419,37 @@ class Geometry(bonsai.core.tool.Geometry):
if edge not in visited_edges:
next_edge = edge
break
if not next_edge:
break
visited_edges.add(next_edge)
next_vert = next_edge.other_vert(current_vert)
# Avoid going back on ourselves
if next_vert not in ordered_verts:
ordered_verts.append(next_vert)
current_vert = next_vert
# Store vertex coordinates in the correct order
new_verts_co = [v.co.copy() for v in ordered_verts]
# Update the stored first vertex position to the new first vertex
mesh["bonsai_first_vert_co"] = new_verts_co[0][:]
# Clear and rebuild mesh with correct vertex order
bm.clear()
# Create new vertices in order
new_verts = [bm.verts.new(co) for co in new_verts_co]
bm.verts.ensure_lookup_table()
# Create edges connecting consecutive vertices
for i in range(len(new_verts) - 1):
bm.edges.new([new_verts[i], new_verts[i + 1]])
# Write back to mesh
bm.to_mesh(mesh)
bm.free()
mesh.update()
mesh.update()