diff --git a/__init__.py b/__init__.py index 5af10d2..4ad08a2 100644 --- a/__init__.py +++ b/__init__.py @@ -6,7 +6,7 @@ from .scripts import phase2_geometry as logic bl_info = { "name": "Stocker Helper", "author": "Gary Ritchie", - "version": (0, 4, 0), + "version": (0, 5, 0), "blender": (4, 0, 0), "location": "View3D > Sidebar > Stocker", "description": "Creates boundary object with Stocker Geometry Nodes assets (sold separately.)", @@ -97,7 +97,7 @@ class STOCKER_OT_setup(bpy.types.Operator): selected_objs = context.selected_objects # 2. Run logic from previous phases - bounds = logic.calculate_global_bounds(selected_objs) + bounds = logic.calculate_global_bounds(selected_objs, parent=selected_objs[0]) if not bounds: self.report({'ERROR'}, "No valid mesh objects selected.") return {'CANCELLED'} diff --git a/scripts/phase2_geometry.py b/scripts/phase2_geometry.py index c26467f..59bb87d 100644 --- a/scripts/phase2_geometry.py +++ b/scripts/phase2_geometry.py @@ -21,10 +21,18 @@ def resolve_to_mesh_objects(obj): return [] -def calculate_global_bounds(selected_objects): +def calculate_global_bounds(selected_objects, parent=None): if not selected_objects: return None + # Determine target coordinate space. When parent is given, compute bounds + # in the parent's local space so the boundary size matches the parent's + # orientation (not world-space AABB which grows with rotation). + if parent: + target_matrix = parent.matrix_world.inverted_safe() + else: + target_matrix = mathutils.Matrix.Identity(4) + min_x, min_y, min_z = float('inf'), float('inf'), float('inf') max_x, max_y, max_z = float('-inf'), float('-inf'), float('-inf') @@ -37,25 +45,22 @@ def calculate_global_bounds(selected_objects): for source_obj in mesh_objects: found_any_mesh = True + # Get world-space corners (use bound_box_world when available) if hasattr(source_obj, 'bound_box_world') and source_obj.bound_box_world: - bbw = source_obj.bound_box_world - for corner in bbw: - min_x = min(min_x, corner.x) - min_y = min(min_y, corner.y) - min_z = min(min_z, corner.z) - max_x = max(max_x, corner.x) - max_y = max(max_y, corner.y) - max_z = max(max_z, corner.z) + world_corners = source_obj.bound_box_world else: matrix = source_obj.matrix_world - for corner in source_obj.bound_box: - world_corner = matrix @ mathutils.Vector(corner) - min_x = min(min_x, world_corner.x) - min_y = min(min_y, world_corner.y) - min_z = min(min_z, world_corner.z) - max_x = max(max_x, world_corner.x) - max_y = max(max_y, world_corner.y) - max_z = max(max_z, world_corner.z) + world_corners = [matrix @ mathutils.Vector(corner) for corner in source_obj.bound_box] + + # Transform to target coordinate space and update AABB + for corner in world_corners: + target_corner = target_matrix @ corner + min_x = min(min_x, target_corner.x) + min_y = min(min_y, target_corner.y) + min_z = min(min_z, target_corner.z) + max_x = max(max_x, target_corner.x) + max_y = max(max_y, target_corner.y) + max_z = max(max_z, target_corner.z) if not found_any_mesh: return None @@ -116,7 +121,10 @@ def create_aligned_boundary_cube(bounds, parent=None, mode=None): mesh.from_pydata(verts, [], faces) mesh.update() - # Parent to selected object so boundary follows instances + # Parent to selected object so boundary follows instances. + # Bounds are already in parent-local space (computed by + # calculate_global_bounds with parent matrix), so location values are + # directly usable after parenting — no additional conversion needed. if parent: obj.parent = parent