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