import bpy # Import the logic from previous phases from .scripts import phase2_geometry as logic bl_info = { "name": "Stocker Helper", "author": "Gary Ritchie", "version": (0, 3, 1), "blender": (4, 0, 0), "location": "View3D > Sidebar > Stocker", "description": "Creates boundary object with Stocker Geometry Nodes assets (sold separately.)", "category": "Object", } def find_and_append_node_group(name): """Search asset libraries for a node group and append it if found.""" import os if name in bpy.data.node_groups: return bpy.data.node_groups[name] # Get libraries, prioritizing those starting with 'stocker' (lowercase) libraries = list(bpy.context.preferences.filepaths.asset_libraries) libraries.sort(key=lambda l: not os.path.basename(l.path).lower().startswith("stocker")) for lib in libraries: if not lib.path or not os.path.exists(lib.path): continue print(f"Stocker: Searching library {lib.path} for {name}...") for root, dirs, files in os.walk(lib.path): for file in files: if file.endswith(".blend"): path = os.path.join(root, file) try: with bpy.data.libraries.load(path) as (data_from, data_to): if name in data_from.node_groups: data_to.node_groups = [name] if name in bpy.data.node_groups: print(f"Stocker: Successfully appended {name} from {path}") return bpy.data.node_groups[name] except: continue return None class STOCKER_PT_panel(bpy.types.Panel): bl_label = "Stocker Helper" bl_idname = "STOCKER_PT_panel" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_category = 'Stocker' def draw(self, context): layout = self.layout col = layout.column(align=True) # New Height Buffer slider col.prop(context.scene, "stocker_height_buffer", text="Height Buffer (%)") col.separator() op_grid = col.operator("object.stocker_setup", text="Grid Boundary") op_grid.mode = 'GRID' op_grid.height_buffer = context.scene.stocker_height_buffer op_circle = col.operator("object.stocker_setup", text="Circle Boundary") op_circle.mode = 'CIRCLE' op_circle.height_buffer = context.scene.stocker_height_buffer class STOCKER_OT_setup(bpy.types.Operator): bl_idname = "object.stocker_setup" bl_label = "Stocker Helper" bl_description = "Generate a boundary cube for the selected objects" bl_options = {'REGISTER', 'UNDO'} mode: bpy.props.EnumProperty( name="Packing Mode", description="Select the geometry nodes template to apply", items=[ ('GRID', "Grid Pack", "Use the Grid Pack template"), ('CIRCLE', "Circle Pack", "Use the Circle Pack template"), ], default='GRID' ) height_buffer: bpy.props.FloatProperty( name="Height Buffer", description="Percentage to add to the Z height (0.1 = 10%)", default=0.1, min=0.0, max=2.0 ) def execute(self, context): # 1. Get selected objects selected_objs = context.selected_objects # 2. Run logic from previous phases bounds = logic.calculate_global_bounds(selected_objs) if not bounds: self.report({'ERROR'}, "No valid mesh objects selected.") return {'CANCELLED'} # Apply the Z height buffer if self.height_buffer > 0: height_add = bounds['size'].z * self.height_buffer bounds['size'].z += height_add bounds['max'].z += height_add # 3. Create the cube (parent to selected instance 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.") return {'CANCELLED'} # 4. Modifier Injection group_name = "grid_pack" if self.mode == 'GRID' else "circle_pack" mod_name = "grid_pack" if self.mode == 'GRID' else "circle_pack" # Try to get or fetch the node group with visual feedback context.window.cursor_modal_set('WAIT') self.report({'INFO'}, f"Searching asset libraries for {group_name}...") try: node_group = find_and_append_node_group(group_name) finally: context.window.cursor_modal_restore() if not node_group: self.report({'WARNING'}, f"Node Group '{group_name}' not found in file or Stocker Asset Libraries.") return {'FINISHED'} mod = new_obj.modifiers.new(name=mod_name, type='NODES') mod.node_group = node_group self.report({'INFO'}, f"Stocker setup complete using {self.mode} mode.") return {'FINISHED'} @classmethod def poll(cls, context): return context.selected_objects classes = ( STOCKER_PT_panel, STOCKER_OT_setup, ) def register(): bpy.types.Scene.stocker_height_buffer = bpy.props.FloatProperty( name="Stocker Height Buffer", description="Default height buffer for new boundaries", default=0.1, min=0.0, max=2.0 ) for cls in classes: bpy.utils.register_class(cls) def unregister(): for cls in reversed(classes): bpy.utils.unregister_class(cls) del bpy.types.Scene.stocker_height_buffer if __name__ == "__main__": register()