diff --git a/__init__.py b/__init__.py index cdb4d95..2561a5f 100644 --- a/__init__.py +++ b/__init__.py @@ -108,7 +108,7 @@ class STOCKER_OT_setup(bpy.types.Operator): bounds['size'].z += height_add bounds['max'].z += height_add - # 3. Create the cube (parent to selected instance for tracking) + # 3. Create the cube (parent to selected for tracking) new_obj = logic.create_aligned_boundary_cube(bounds, parent=selected_objs[0], mode=self.mode) if not new_obj: self.report({'ERROR'}, "Failed to create boundary cube.") diff --git a/scripts/phase2_geometry.py b/scripts/phase2_geometry.py index f1c6842..c4dcd15 100644 --- a/scripts/phase2_geometry.py +++ b/scripts/phase2_geometry.py @@ -1,6 +1,25 @@ import bpy import mathutils +def resolve_to_mesh_objects(obj): + """Recursively resolve collection instances and linked instances to their source mesh objects.""" + if obj.type == 'MESH': + return [obj] + elif obj.type == 'EMPTY' and obj.instance_collection: + # Collection instance — walk linked objects + result = [] + for linked_obj in obj.instance_collection.objects: + result.extend(resolve_to_mesh_objects(linked_obj)) + return result + elif obj.type == 'MESH' or obj.instance_type != 'NONE': + # Linked instance or other instance type — use its data + if obj.data and obj.data.type == 'MESH': + return [obj] + elif hasattr(obj, 'instance_object') and obj.instance_object: + return resolve_to_mesh_objects(obj.instance_object) + return [] + + def calculate_global_bounds(selected_objects): if not selected_objects: return None @@ -10,20 +29,32 @@ def calculate_global_bounds(selected_objects): found_any_mesh = False for obj in selected_objects: - if obj.type != 'MESH': + mesh_objects = resolve_to_mesh_objects(obj) + if not mesh_objects: continue - - found_any_mesh = True - matrix = obj.matrix_world - for corner in 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) + + for source_obj in mesh_objects: + found_any_mesh = True + + 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) + 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) if not found_any_mesh: return None @@ -34,37 +65,56 @@ def calculate_global_bounds(selected_objects): 'size': mathutils.Vector((max_x - min_x, max_y - min_y, max_z - min_z)) } -def create_aligned_boundary_cube(bounds): +def create_aligned_boundary_cube(bounds, parent=None, mode=None): if not bounds: return None min_v = bounds['min'] + max_v = bounds['max'] size = bounds['size'] mesh = bpy.data.meshes.new("Stocker_Boundary") obj = bpy.data.objects.new("Stocker_Boundary", mesh) bpy.context.collection.objects.link(obj) - verts = [ - (0, 0, 0), - (size.x, 0, 0), - (size.x, -size.y, 0), - (0, -size.y, 0), - (0, 0, size.z), - (size.x, 0, size.z), - (size.x, -size.y, size.z), - (0, -size.y, size.z) - ] + if mode == 'GRID': + # Origin at top-front-left: (min_x, max_y, min_z) — cube extends +X, -Y, +Z + obj.location = (min_v.x, max_v.y, min_v.z) + verts = [ + (0, 0, 0), + (size.x, 0, 0), + (size.x, -size.y, 0), + (0, -size.y, 0), + (0, 0, size.z), + (size.x, 0, size.z), + (size.x, -size.y, size.z), + (0, -size.y, size.z) + ] + else: + # CIRCLE mode: origin at bottom-front-left + obj.location = min_v.copy() + verts = [ + (0, 0, 0), + (size.x, 0, 0), + (size.x, size.y, 0), + (0, size.y, 0), + (0, 0, size.z), + (size.x, 0, size.z), + (size.x, size.y, size.z), + (0, size.y, size.z) + ] faces = [ (0, 1, 2, 3), (4, 5, 6, 7), (0, 1, 5, 4), (1, 2, 6, 5), (2, 3, 7, 6), (3, 0, 4, 7) ] - + mesh.from_pydata(verts, [], faces) mesh.update() - - # Place origin at Back-Left (Min X, Max Y, Min Z) - obj.location = (bounds['min'].x, bounds['max'].y, bounds['min'].z) + + # Parent to selected object so boundary follows instances + if parent: + obj.parent = parent + return obj if __name__ == "__main__":