Public Access
2
0
Files
stocker_helper/scripts/phase2_geometry.py
T

130 lines
4.4 KiB
Python

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
min_x, min_y, min_z = float('inf'), float('inf'), float('inf')
max_x, max_y, max_z = float('-inf'), float('-inf'), float('-inf')
found_any_mesh = False
for obj in selected_objects:
mesh_objects = resolve_to_mesh_objects(obj)
if not mesh_objects:
continue
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
return {
'min': mathutils.Vector((min_x, min_y, min_z)),
'max': mathutils.Vector((max_x, max_y, max_z)),
'size': mathutils.Vector((max_x - min_x, max_y - min_y, max_z - min_z))
}
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)
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 center X,Y, min Z — cube extends equally in all X/Y directions
center_x = (min_v.x + max_v.x) / 2
center_y = (min_v.y + max_v.y) / 2
obj.location = (center_x, center_y, min_v.z)
verts = [
(-size.x / 2, -size.y / 2, 0),
(size.x / 2, -size.y / 2, 0),
(size.x / 2, size.y / 2, 0),
(-size.x / 2, size.y / 2, 0),
(-size.x / 2, -size.y / 2, size.z),
(size.x / 2, -size.y / 2, size.z),
(size.x / 2, size.y / 2, size.z),
(-size.x / 2, size.y / 2, 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()
# Parent to selected object so boundary follows instances
if parent:
obj.parent = parent
# QoL: wire display and auto-select for immediate modifier edits
obj.display_type = 'WIRE'
obj.select_set(True)
return obj
if __name__ == "__main__":
print("Phase 2: Geometry (Origin Alignment) logic loaded.")