125 lines
3.7 KiB
Python
125 lines
3.7 KiB
Python
import bpy
|
||
import os
|
||
import math
|
||
import re
|
||
from mathutils import Vector
|
||
|
||
# --- Settings ---
|
||
source_dir = r"D:\Dropbox\Gitea_OD\Entourage\People\WIP - Lowpoly People - 2"
|
||
spacing = 2.0
|
||
|
||
# --- Helpers ---
|
||
def extract_base_name(name):
|
||
# if re.fullmatch(r".+\.\d{3}", name): # e.g. Tree.001
|
||
# return name[:-4]
|
||
# if re.fullmatch(r".+\d{3}", name): # e.g. Tree001
|
||
# return re.sub(r"\d{3}$", "", name)
|
||
return name
|
||
|
||
def get_collection_bounds(collection):
|
||
depsgraph = bpy.context.evaluated_depsgraph_get()
|
||
points = []
|
||
|
||
for obj in collection.objects:
|
||
obj_eval = obj.evaluated_get(depsgraph)
|
||
points += [obj_eval.matrix_world @ Vector(v) for v in obj_eval.bound_box]
|
||
|
||
if not points:
|
||
return 0, 0, 0
|
||
|
||
x_vals = [v.x for v in points]
|
||
y_vals = [v.y for v in points]
|
||
z_vals = [v.z for v in points]
|
||
|
||
width = max(x_vals) - min(x_vals)
|
||
depth = max(y_vals) - min(y_vals)
|
||
height = max(z_vals) - min(z_vals)
|
||
|
||
return width, depth, height
|
||
|
||
# --- Main ---
|
||
collection_instances = []
|
||
global_existing_names = set()
|
||
|
||
print(f"\n🔍 Searching for .blend files in: {source_dir}\n")
|
||
|
||
for filename in sorted(os.listdir(source_dir)):
|
||
if not filename.endswith(".blend"):
|
||
continue
|
||
|
||
blend_path = os.path.join(source_dir, filename)
|
||
blend_base = os.path.splitext(filename)[0]
|
||
print(f"\n📄 Processing file: {filename}")
|
||
|
||
# Load all objects from file
|
||
with bpy.data.libraries.load(blend_path, link=True) as (data_from, data_to):
|
||
base_name_seen = set()
|
||
unique_object_names = []
|
||
|
||
for name in data_from.objects:
|
||
base = extract_base_name(name)
|
||
if base in base_name_seen:
|
||
print(f" ⛔ Skipping variant: {name} (base: {base})")
|
||
continue
|
||
if name in global_existing_names:
|
||
print(f" 🔁 Already linked globally: {name}")
|
||
continue
|
||
base_name_seen.add(base)
|
||
unique_object_names.append(name)
|
||
|
||
data_to.objects = unique_object_names
|
||
|
||
if not data_to.objects:
|
||
print(f"⚠️ No unique objects to import from {filename}")
|
||
continue
|
||
|
||
# Create a collection and instance per object
|
||
for obj in data_to.objects:
|
||
if not obj:
|
||
continue
|
||
|
||
obj_name = obj.name
|
||
col_name = f"COL_{blend_base}_{obj_name}"
|
||
inst_name = f"Instance_{blend_base}_{obj_name}"
|
||
|
||
# Create and link the collection
|
||
new_col = bpy.data.collections.new(col_name)
|
||
bpy.context.scene.collection.children.link(new_col)
|
||
new_col.objects.link(obj)
|
||
global_existing_names.add(obj.name)
|
||
print(f" ➕ Imported object: {obj.name} → {col_name}")
|
||
|
||
# Create instance of the collection
|
||
instance_obj = bpy.data.objects.new(name=inst_name, object_data=None)
|
||
instance_obj.instance_type = 'COLLECTION'
|
||
instance_obj.instance_collection = new_col
|
||
bpy.context.collection.objects.link(instance_obj)
|
||
collection_instances.append((instance_obj, new_col))
|
||
print(f" 🔗 Created instance: {inst_name}")
|
||
|
||
# --- Arrange in Grid ---
|
||
print("\n📐 Arranging collection instances in grid...\n")
|
||
|
||
cols = math.ceil(math.sqrt(len(collection_instances)))
|
||
x_cursor = 0
|
||
y_cursor = 0
|
||
max_row_depth = 0
|
||
col = 0
|
||
|
||
for instance_obj, collection in collection_instances:
|
||
width, depth, _ = get_collection_bounds(collection)
|
||
|
||
instance_obj.location = (x_cursor, y_cursor, 0)
|
||
print(f"📍 Placing '{instance_obj.name}' at (X: {x_cursor:.2f}, Y: {y_cursor:.2f})")
|
||
|
||
x_cursor += width + spacing
|
||
max_row_depth = max(max_row_depth, depth)
|
||
|
||
col += 1
|
||
if col >= cols:
|
||
col = 0
|
||
x_cursor = 0
|
||
y_cursor += max_row_depth + spacing
|
||
max_row_depth = 0
|
||
|
||
print("\n✅ Done.")
|