You've already forked stocker_helper
Compare commits
7 Commits
e157c29cff
...
1548ebf4ff
| Author | SHA1 | Date | |
|---|---|---|---|
| 1548ebf4ff | |||
| 5980dfc770 | |||
| 376a654f4f | |||
| 9e26aa36d3 | |||
| f8f767c850 | |||
| 6b95e85ccb | |||
| f1067a3c0b |
@@ -22,7 +22,7 @@ The **Stocker Helper** creates a boundary cube that encompasses selected objects
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. Download `stocker_helper.zip`.
|
1. Download `stocker.zip`.
|
||||||
2. Drag the `.zip` file into Blender.
|
2. Drag the `.zip` file into Blender.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|||||||
+4
-4
@@ -6,10 +6,10 @@ from .scripts import phase2_geometry as logic
|
|||||||
bl_info = {
|
bl_info = {
|
||||||
"name": "Stocker Helper",
|
"name": "Stocker Helper",
|
||||||
"author": "Gary Ritchie",
|
"author": "Gary Ritchie",
|
||||||
"version": (0, 1, 0),
|
"version": (0, 3, 1),
|
||||||
"blender": (4, 0, 0),
|
"blender": (4, 0, 0),
|
||||||
"location": "View3D > Sidebar > Stocker",
|
"location": "View3D > Sidebar > Stocker",
|
||||||
"description": "Automates boundary creation for Geometry Nodes packing",
|
"description": "Creates boundary object with Stocker Geometry Nodes assets (sold separately.)",
|
||||||
"category": "Object",
|
"category": "Object",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,8 +108,8 @@ class STOCKER_OT_setup(bpy.types.Operator):
|
|||||||
bounds['size'].z += height_add
|
bounds['size'].z += height_add
|
||||||
bounds['max'].z += height_add
|
bounds['max'].z += height_add
|
||||||
|
|
||||||
# 3. Create the cube
|
# 3. Create the cube (parent to selected for tracking)
|
||||||
new_obj = logic.create_aligned_boundary_cube(bounds)
|
new_obj = logic.create_aligned_boundary_cube(bounds, parent=selected_objs[0], mode=self.mode)
|
||||||
if not new_obj:
|
if not new_obj:
|
||||||
self.report({'ERROR'}, "Failed to create boundary cube.")
|
self.report({'ERROR'}, "Failed to create boundary cube.")
|
||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+90
-28
@@ -1,6 +1,26 @@
|
|||||||
import bpy
|
import bpy
|
||||||
import mathutils
|
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):
|
def calculate_global_bounds(selected_objects):
|
||||||
if not selected_objects:
|
if not selected_objects:
|
||||||
return None
|
return None
|
||||||
@@ -10,20 +30,32 @@ def calculate_global_bounds(selected_objects):
|
|||||||
|
|
||||||
found_any_mesh = False
|
found_any_mesh = False
|
||||||
for obj in selected_objects:
|
for obj in selected_objects:
|
||||||
if obj.type != 'MESH':
|
mesh_objects = resolve_to_mesh_objects(obj)
|
||||||
|
if not mesh_objects:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
found_any_mesh = True
|
for source_obj in mesh_objects:
|
||||||
matrix = obj.matrix_world
|
found_any_mesh = True
|
||||||
for corner in obj.bound_box:
|
|
||||||
world_corner = matrix @ mathutils.Vector(corner)
|
if hasattr(source_obj, 'bound_box_world') and source_obj.bound_box_world:
|
||||||
|
bbw = source_obj.bound_box_world
|
||||||
min_x = min(min_x, world_corner.x)
|
for corner in bbw:
|
||||||
min_y = min(min_y, world_corner.y)
|
min_x = min(min_x, corner.x)
|
||||||
min_z = min(min_z, world_corner.z)
|
min_y = min(min_y, corner.y)
|
||||||
max_x = max(max_x, world_corner.x)
|
min_z = min(min_z, corner.z)
|
||||||
max_y = max(max_y, world_corner.y)
|
max_x = max(max_x, corner.x)
|
||||||
max_z = max(max_z, world_corner.z)
|
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:
|
if not found_any_mesh:
|
||||||
return None
|
return None
|
||||||
@@ -34,37 +66,67 @@ def calculate_global_bounds(selected_objects):
|
|||||||
'size': mathutils.Vector((max_x - min_x, max_y - min_y, max_z - min_z))
|
'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:
|
if not bounds:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
min_v = bounds['min']
|
min_v = bounds['min']
|
||||||
|
max_v = bounds['max']
|
||||||
size = bounds['size']
|
size = bounds['size']
|
||||||
|
|
||||||
mesh = bpy.data.meshes.new("Stocker_Boundary")
|
mesh = bpy.data.meshes.new("Stocker_Boundary")
|
||||||
obj = bpy.data.objects.new("Stocker_Boundary", mesh)
|
obj = bpy.data.objects.new("Stocker_Boundary", mesh)
|
||||||
bpy.context.collection.objects.link(obj)
|
bpy.context.collection.objects.link(obj)
|
||||||
|
|
||||||
verts = [
|
if mode == 'GRID':
|
||||||
(0, 0, 0),
|
# Origin at top-front-left: (min_x, max_y, min_z) — cube extends +X, -Y, +Z
|
||||||
(size.x, 0, 0),
|
obj.location = (min_v.x, max_v.y, min_v.z)
|
||||||
(size.x, -size.y, 0),
|
verts = [
|
||||||
(0, -size.y, 0),
|
(0, 0, 0),
|
||||||
(0, 0, size.z),
|
(size.x, 0, 0),
|
||||||
(size.x, 0, size.z),
|
(size.x, -size.y, 0),
|
||||||
(size.x, -size.y, size.z),
|
(0, -size.y, 0),
|
||||||
(0, -size.y, size.z)
|
(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 = [
|
faces = [
|
||||||
(0, 1, 2, 3), (4, 5, 6, 7), (0, 1, 5, 4),
|
(0, 1, 2, 3), (4, 5, 6, 7), (0, 1, 5, 4),
|
||||||
(1, 2, 6, 5), (2, 3, 7, 6), (3, 0, 4, 7)
|
(1, 2, 6, 5), (2, 3, 7, 6), (3, 0, 4, 7)
|
||||||
]
|
]
|
||||||
|
|
||||||
mesh.from_pydata(verts, [], faces)
|
mesh.from_pydata(verts, [], faces)
|
||||||
mesh.update()
|
mesh.update()
|
||||||
|
|
||||||
# Place origin at Back-Left (Min X, Max Y, Min Z)
|
# Parent to selected object so boundary follows instances
|
||||||
obj.location = (bounds['min'].x, bounds['max'].y, bounds['min'].z)
|
if parent:
|
||||||
|
obj.parent = parent
|
||||||
|
|
||||||
|
# QoL: deselect all, then select only the boundary for immediate modifier edits
|
||||||
|
bpy.ops.object.select_all(action='DESELECT')
|
||||||
|
# obj.display_type = 'WIRE'
|
||||||
|
obj.select_set(True)
|
||||||
|
# Make it the active object
|
||||||
|
bpy.context.view_layer.objects.active = obj
|
||||||
|
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user