Created
March 16, 2025 19:19
-
-
Save Mockapapella/ca9c591acdf57cf69268ad274cb374fd to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import bpy, random, math, time | |
from functools import partial | |
from collections import defaultdict | |
def create_circuit_pattern(w=30, h=160, trace_t=0.05, via_r=0.075, min_len=6, max_len=20, min_s=1, board_t=0.2, timeout=600, | |
chip_w=8, chip_h=8, chip_height=0.125, max_traces=None, density_factor=0.25, pin_spacing=1, | |
grid_spacing=0.25, color_transition_margin=0.4, emission_intensity=3.0, render_samples=128, | |
use_sector_based_filling=True, chip_emanating_trace_percentage=0.9, y_direction_bias=3.0): | |
# Initialize core variables and clear scene | |
start, count = time.time(), 0 | |
max_traces = max_traces or int((w * h - chip_w * chip_h) * density_factor) | |
[obj.select_set(True) for obj in bpy.context.scene.objects]; bpy.ops.object.delete() | |
# Core utility functions with one-line definitions | |
grid_to_world, world_to_grid = lambda x, y, z=0: (x * grid_spacing, y * grid_spacing, z), lambda x, y: (int(x / grid_spacing), int(y / grid_spacing)) | |
is_valid_pos, get_distance = lambda x, y: 0 <= x < w and 0 <= y < h and grid.get((x, y), -3) == -1, lambda x1, y1, x2, y2: abs(x2-x1) + abs(y2-y1) | |
# Material creation with node setup - no fallbacks | |
def create_mat(name, base_color, emission_color=None, emission_strength=0, metallic=0, roughness=1, mix_factor=0): | |
mat = bpy.data.materials.new(name=name); mat.use_nodes = True | |
nodes, links = mat.node_tree.nodes, mat.node_tree.links; [nodes.remove(node) for node in list(nodes)] | |
output, principled = nodes.new('ShaderNodeOutputMaterial'), nodes.new('ShaderNodeBsdfPrincipled') | |
principled.inputs[0].default_value, principled.inputs.get('Metallic').default_value, principled.inputs.get('Roughness').default_value = base_color, metallic, roughness | |
if emission_color and emission_strength > 0: | |
emission, mix = nodes.new('ShaderNodeEmission'), nodes.new('ShaderNodeMixShader') | |
emission.inputs[0].default_value, emission.inputs[1].default_value, mix.inputs[0].default_value = emission_color, emission_strength, mix_factor | |
links.new(principled.outputs[0], mix.inputs[1]); links.new(emission.outputs[0], mix.inputs[2]); links.new(mix.outputs[0], output.inputs[0]) | |
else: links.new(principled.outputs[0], output.inputs[0]) | |
return mat | |
# Get color based on position from chip for traces and vias | |
def get_trace_color(y_pos): | |
blue, red = (0.0, 0.1, 0.9, 1.0), (0.95, 0.0, 0.0, 1.0) | |
margin = chip_h * color_transition_margin | |
transition_bottom, transition_top = max(0, chip_y - margin), min(h, chip_y + chip_h + margin) | |
factor = 0 if y_pos <= transition_bottom else 1 if y_pos >= transition_top else (y_pos - transition_bottom) / (transition_top - transition_bottom) | |
rgb = [blue[i] + (red[i] - blue[i]) * factor for i in range(3)] | |
intensity = 1.0 + 0.3 * (1.0 - sum(rgb[:3])/3) if sum(rgb[:3]) > 0 else 1.0 | |
return tuple([min(1.0, c * intensity) for c in rgb[:3]] + [1.0]) | |
# Create board and chip materials | |
board_mat = create_mat("BoardMaterial", (0.02, 0.05, 0.02, 1), (0.02, 0.05, 0.02, 1), 0.05, 0.1, 0.8, 0.05) | |
# Create true black chip material with edge highlights | |
chip_mat = bpy.data.materials.new(name="TrueBlackChip"); chip_mat.use_nodes = True | |
nodes, links = chip_mat.node_tree.nodes, chip_mat.node_tree.links; [nodes.remove(node) for node in list(nodes)] | |
output, principled, emission, mix_shader, fresnel = [nodes.new(t) for t in | |
['ShaderNodeOutputMaterial', 'ShaderNodeBsdfPrincipled', 'ShaderNodeEmission', 'ShaderNodeMixShader', 'ShaderNodeFresnel']] | |
principled.inputs['Base Color'].default_value, principled.inputs['Metallic'].default_value, principled.inputs['Roughness'].default_value = (0.0, 0.0, 0.0, 1.0), 0.2, 0.2 | |
emission.inputs['Color'].default_value, emission.inputs['Strength'].default_value, fresnel.inputs['IOR'].default_value = (0.05, 0.1, 0.3, 1.0), 1.5, 1.7 | |
[links.new(*ln) for ln in [(fresnel.outputs['Fac'], mix_shader.inputs[0]), (principled.outputs[0], mix_shader.inputs[1]), | |
(emission.outputs[0], mix_shader.inputs[2]), (mix_shader.outputs[0], output.inputs[0])]] | |
# Create board and chip in one block | |
board_width, board_height = w * grid_spacing, h * grid_spacing | |
bpy.ops.mesh.primitive_cube_add(size=1, location=(board_width/2, board_height/2, -board_t/2)) | |
board = bpy.context.active_object; board.name, board.scale = "CircuitBoard", (board_width+grid_spacing, board_height+grid_spacing, board_t) | |
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True); board.data.materials.append(board_mat) | |
# Setup grid and create chip | |
grid = {(x, y): -1 for x in range(w) for y in range(h)} | |
chip_x, chip_y = w // 2 - chip_w // 2, h // 2 - chip_h // 2 | |
# Create chip with beveled edges | |
bpy.ops.mesh.primitive_cube_add(size=1, location=(chip_x * grid_spacing + chip_w * grid_spacing/2, chip_y * grid_spacing + chip_h * grid_spacing/2, chip_height/2)) | |
chip = bpy.context.active_object; chip.name, chip.scale = "Chip", (chip_w * grid_spacing, chip_h * grid_spacing, chip_height * 1.2) | |
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) | |
bpy.context.active_object.modifiers.new(name="Bevel", type='BEVEL').width, bpy.context.active_object.modifiers["Bevel"].segments = 0.02, 3 | |
chip.data.materials.append(chip_mat) | |
# Create hourglass symbol with one function call and condensed code | |
def create_hourglass_symbol(): | |
symbol_location = (chip_x * grid_spacing + chip_w * grid_spacing/2, chip_y * grid_spacing + chip_h * grid_spacing/2, chip_height * 1.2 + 0.002) | |
h_width, h_height = min(chip_w, chip_h) * grid_spacing * 0.3, min(chip_w, chip_h) * grid_spacing * 0.48 | |
verts = [(h_height/2, -h_width, 0), (h_height/2, h_width, 0), (0, 0, 0), (-h_height/2, -h_width, 0), (-h_height/2, h_width, 0)] | |
faces = [[0, 1, 2], [2, 3, 4]] | |
hourglass_mesh = bpy.data.meshes.new("Hourglass_Mesh"); hourglass_mesh.from_pydata(verts, [], faces); hourglass_mesh.update() | |
symbol = bpy.data.objects.new("Hourglass_Symbol", hourglass_mesh); bpy.context.collection.objects.link(symbol); symbol.location = symbol_location | |
# Steel material for symbol - one-shot node creation | |
steel_mat = bpy.data.materials.new(name="GlowingSteel"); steel_mat.use_nodes = True | |
nodes, links = steel_mat.node_tree.nodes, steel_mat.node_tree.links; [nodes.remove(node) for node in list(nodes)] | |
output, principled, emission, mix = [nodes.new(t) for t in ['ShaderNodeOutputMaterial', 'ShaderNodeBsdfPrincipled', 'ShaderNodeEmission', 'ShaderNodeMixShader']] | |
principled.inputs['Base Color'].default_value, principled.inputs['Metallic'].default_value, principled.inputs['Roughness'].default_value = (0.95, 0.95, 0.98, 1.0), 1.0, 0.15 | |
emission.inputs['Color'].default_value, emission.inputs['Strength'].default_value, mix.inputs[0].default_value = (1.0, 1.0, 1.0, 1.0), 2.0, 0.3 | |
[links.new(*ln) for ln in [(principled.outputs[0], mix.inputs[1]), (emission.outputs[0], mix.inputs[2]), (mix.outputs[0], output.inputs[0])]] | |
symbol.data.materials.append(steel_mat) | |
symbol.modifiers.new(name="Bevel", type='BEVEL').width, symbol.modifiers["Bevel"].segments = 0.003, 2 | |
symbol.modifiers.new(name="Solidify", type='SOLIDIFY').thickness = 0.004 | |
return symbol | |
# Create hourglass symbol and mark chip area in grid | |
hourglass = create_hourglass_symbol() | |
for x in range(chip_x, chip_x+chip_w+1): | |
for y in range(chip_y, chip_y+chip_h+1): | |
if 0 <= x < w and 0 <= y < h: grid[(x, y)] = -3 | |
# Direction vectors and movement validation (0=right, 1=right-up, 2=up, 3=left-up, 4=left, 5=left-down, 6=down, 7=right-down) | |
dirs = [(1,0), (1,1), (0,1), (-1,1), (-1,0), (-1,-1), (0,-1), (1,-1)] | |
# Direction bias calculation in one line | |
dir_bias = lambda i: 1.0 + (abs(y_direction_bias) * (2.0 if i in [2,6] else 1.0 if i in [1,3,5,7] else 0.0) | |
* (1.0 if (i == 2 and y_direction_bias > 0) or (i == 6 and y_direction_bias < 0) else | |
0.7 if (i in [1,3] and y_direction_bias > 0) or (i in [5,7] and y_direction_bias < 0) else 0.3)) | |
# Path validation functions with self-crossing prevention | |
is_valid_move = lambda x1, y1, x2, y2: is_valid_pos(x2, y2) and not (abs(x2-x1) == 1 and abs(y2-y1) == 1 and (grid.get((x1, y2), -3) != -1 or grid.get((x2, y1), -3) != -1)) | |
# Only allow 45-degree turns (not 135-degree sharp turns) | |
turns = lambda i: [i, (i-1)%8, (i+1)%8] # Current direction and 45-degree turns only | |
# Prevent backwards or sharp turns by checking the opposite of current direction | |
is_sharp_turn = lambda current, new: abs(current - new) > 2 and abs(current - new) < 6 | |
can_go = lambda x, y, i, steps=1: all(is_valid_move(x+dirs[i][0]*(j-1), y+dirs[i][1]*(j-1), x+dirs[i][0]*j, y+dirs[i][1]*j) for j in range(1, steps+1)) | |
# Optimized via creation | |
def via(x, y): | |
if not (0 <= x < w and 0 <= y < h): return False | |
world_x, world_y, world_z = grid_to_world(x, y) | |
via_color = get_trace_color(y) | |
via_material = create_mat(f"ViaMaterial_{x}_{y}", via_color, via_color, emission_intensity, 0, 0, 1.0) | |
# Create torus for via - no fallback needed | |
bpy.ops.mesh.primitive_torus_add(major_segments=32, minor_segments=16, major_radius=via_r, minor_radius=via_r/2, location=(world_x, world_y, world_z)) | |
v = bpy.context.active_object; v.scale = (1, 1, board_t*0.85) | |
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) | |
v.name, grid[(x, y)] = f"Via_{x}_{y}", -2 | |
v.data.materials.append(via_material) | |
return True | |
# Trace creation with gradient materials and precise endpoints | |
def trace(path, tid, start_via=True, end_via=True): | |
if len(path) < min_len or any(not is_valid_move(path[i-1][0], path[i-1][1], path[i][0], path[i][1]) for i in range(1, len(path))): | |
return False | |
# Create vias at endpoints | |
if start_via: via(path[0][0], path[0][1]) | |
if end_via: via(path[-1][0], path[-1][1]) | |
# Create trace curve with gradient material | |
c = bpy.data.curves.new(f'Trace_{tid}', 'CURVE') | |
c.dimensions, c.resolution_u, c.bevel_depth, c.bevel_resolution, c.use_fill_caps = '3D', 12, trace_t/2, 6, True | |
obj = bpy.data.objects.new(f'Trace_{tid}', c); bpy.context.collection.objects.link(obj) | |
# Create gradient material - optimized node setup | |
mat = bpy.data.materials.new(name=f"TraceMaterial_{tid}"); mat.use_nodes = True | |
nodes, links = mat.node_tree.nodes, mat.node_tree.links; [nodes.remove(node) for node in nodes] | |
output = nodes.new('ShaderNodeOutputMaterial') | |
# Create color gradient for trace with increased emission | |
color_ramp, tex_coord, emission = [nodes.new(t) for t in ['ShaderNodeValToRGB', 'ShaderNodeTexCoord', 'ShaderNodeEmission']] | |
color_ramp.color_ramp.elements[0].color, color_ramp.color_ramp.elements[1].color = get_trace_color(path[0][1]), get_trace_color(path[-1][1]) | |
emission.inputs[1].default_value = emission_intensity * 1.5 | |
[links.new(*ln) for ln in [(tex_coord.outputs['Object'], color_ramp.inputs[0]), (color_ramp.outputs['Color'], emission.inputs[0]), | |
(emission.outputs[0], output.inputs['Surface'])]] | |
obj.data.materials.append(mat) | |
# Create spline with adjusted endpoints | |
spline = c.splines.new('POLY'); spline.points.add(len(path) - 1) | |
for i, (x, y) in enumerate(path): | |
grid[(x, y)] = tid # Mark grid | |
world_x, world_y, world_z = grid_to_world(x, y) | |
adjusted_pos = [world_x, world_y, world_z] | |
# Adjust endpoints for smooth via connections | |
if (i == 0 and start_via) or (i == len(path)-1 and end_via) and len(path) >= 2: | |
next_x, next_y = path[1] if i == 0 else path[-2] | |
next_world_x, next_world_y, _ = grid_to_world(next_x, next_y) | |
dir_x, dir_y = world_x - next_world_x, world_y - next_world_y | |
dist = math.sqrt(dir_x**2 + dir_y**2) | |
if dist > 0: | |
dir_x, dir_y = dir_x/dist, dir_y/dist | |
adjusted_pos[0], adjusted_pos[1] = world_x - dir_x * via_r * 1.05, world_y - dir_y * via_r * 1.05 | |
spline.points[i].co = (*adjusted_pos, 1) | |
return True | |
# Path generation with target-based and random routing - optimized for density | |
def generate_path(start_x, start_y, target_x=None, target_y=None, max_length=max_len, min_length=min_len): | |
path, path_set = [(start_x, start_y)], {(start_x, start_y)} | |
# Direction selection - target-based or random with y-direction bias | |
if target_x is None: | |
options = [d for d in range(8) if can_go(start_x, start_y, d, min_s)] | |
if not options: return [] | |
# Apply direction bias with weighted random selection | |
if abs(y_direction_bias) > 0.001: | |
weights = [dir_bias(d) for d in options] | |
r, cumulative_weight = random.random() * sum(weights), 0 | |
for i, w in enumerate(weights): | |
if (cumulative_weight := cumulative_weight + w) >= r: | |
dir_idx = options[i] | |
break | |
else: | |
dir_idx = random.choice(options) | |
else: | |
# Target-based direction selection | |
dx, dy = (1 if target_x > start_x else -1 if target_x < start_x else 0), (1 if target_y > start_y else -1 if target_y < start_y else 0) | |
dir_match = next((i for i, d in enumerate(dirs) if d == (dx, dy)), None) | |
options = [d for d in range(8) if can_go(start_x, start_y, d, 1)] | |
dir_idx = dir_match if dir_match is not None and dir_match in options else (random.choice(options) if options else None) | |
if dir_idx is None: return [] | |
# Path growth with obstacle avoidance and target seeking | |
d, x, y, straight = dirs[dir_idx], start_x, start_y, 0 | |
target_length = random.randint(min_length, max_length) if target_x is None else 100 | |
# Path extension loop - generates path with turning | |
while len(path) < target_length: | |
if target_x is not None and x == target_x and y == target_y: break | |
nx, ny = x + d[0], y + d[1] | |
# Handle obstacles with weighted direction change | |
if (nx, ny) in path_set or not is_valid_move(x, y, nx, ny): | |
options = [(i, abs(x+dirs[i][0] - (target_x or 0)) + abs(y+dirs[i][1] - (target_y or 0)) if target_x is not None else 0) | |
for i in turns(dir_idx) if is_valid_move(x, y, x+dirs[i][0], y+dirs[i][1]) and (x+dirs[i][0], y+dirs[i][1]) not in path_set] | |
if not options: return path if len(path) >= min_length else [] | |
# Apply direction bias for non-targeted paths | |
if target_x is None and abs(y_direction_bias) > 0.001: | |
weighted_options = [(i, dir_bias(i), s) for i, s in options] | |
r, cumulative_weight = random.random() * sum(w for _, w, _ in weighted_options), 0 | |
for i, w, _ in weighted_options: | |
if (cumulative_weight := cumulative_weight + w) >= r: | |
dir_idx = i | |
break | |
else: | |
dir_idx = min(options, key=lambda o: o[1])[0] if target_x is not None else random.choice([o[0] for o in options]) | |
d, straight = dirs[dir_idx], 0 | |
continue | |
# Add point to path and consider random turns | |
x, y = nx, ny | |
path.append((x, y)) | |
path_set.add((x, y)) | |
straight += 1 | |
if straight >= min_s and random.random() < 0.2 and len(path) < target_length - min_s: | |
turn_options = [(i, abs(x+dirs[i][0] - (target_x or 0)) + abs(y+dirs[i][1] - (target_y or 0)) if target_x is not None else 0) | |
for i in turns(dir_idx) if i != dir_idx and is_valid_move(x, y, x+dirs[i][0], y+dirs[i][1]) | |
and (x+dirs[i][0], y+dirs[i][1]) not in path_set] | |
if turn_options: | |
dir_idx = min(turn_options, key=lambda o: o[1])[0] if target_x is not None else random.choice([o[0] for o in turn_options]) | |
d, straight = dirs[dir_idx], 0 | |
return path if len(path) >= min_length else [] | |
# Three-phase trace generation with timing and statistics | |
def generate_traces(): | |
nonlocal count | |
gen_stats = {'phase0_traces': 0, 'phase0_time': 0, 'phase1_traces': 0, 'phase1_time': 0, 'phase2_traces': 0, 'phase2_time': 0, 'sectors_filled': 0} | |
# Phase 0: Chip-emanating traces | |
phase0_start = time.time() | |
chip_points = [(x, y) for x in range(chip_x, chip_x+chip_w+1) for y in range(chip_y, chip_y+chip_h+1) | |
if 0 <= x < w and 0 <= y < h and grid.get((x, y), 0) == -3 and | |
any(0 <= nx < w and 0 <= ny < h and grid.get((nx, ny), 0) != -3 for dx, dy in dirs if (nx := x + dx) and (ny := y + dy))] | |
# Calculate chip trace allocation and shuffle starting points | |
desired_chip_traces = int(max_traces * chip_emanating_trace_percentage) | |
traces_per_point = max(1, desired_chip_traces // (len(chip_points) or 1)) | |
chip_traces_created = 0 | |
expanded_chip_points = [point for point in chip_points for _ in range(traces_per_point)] | |
random.shuffle(expanded_chip_points) | |
# Generate chip-emanating traces | |
for attempt in range(min(len(expanded_chip_points), desired_chip_traces * 2)): | |
if chip_traces_created >= desired_chip_traces or time.time() - start >= timeout: break | |
start_x, start_y = expanded_chip_points[attempt % len(expanded_chip_points)] | |
# Find valid directions away from chip | |
valid_dirs = [] | |
for i, (dx, dy) in enumerate(dirs): | |
nx, ny = start_x + dx, start_y + dy | |
if not (0 <= nx < w and 0 <= ny < h) or grid.get((nx, ny), 0) != -1: continue | |
path_away, can_extend = [(start_x, start_y), (nx, ny)], True | |
current_x, current_y = nx, ny | |
# Try to extend path away from chip | |
for _ in range(2): | |
found_next = False | |
for next_dir in turns(i): | |
next_x, next_y = current_x + dirs[next_dir][0], current_y + dirs[next_dir][1] | |
if is_valid_move(current_x, current_y, next_x, next_y): | |
path_away.append((next_x, next_y)); current_x, current_y, i, found_next = next_x, next_y, next_dir, True | |
break | |
if not found_next: can_extend = False; break | |
if can_extend: valid_dirs.append((i, path_away)) | |
if not valid_dirs: continue | |
# Choose direction and extend path | |
chosen_dir, initial_path = random.choice(valid_dirs) | |
x, y = initial_path[-1] | |
extended_path, path_set, d, straight = initial_path.copy(), set(initial_path), dirs[chosen_dir], 0 | |
# Extend path with random turning | |
remaining_length = random.randint(2, max_len - len(extended_path)) | |
for _ in range(remaining_length): | |
nx, ny = x + d[0], y + d[1] | |
# Handle obstacles with direction change | |
if (nx, ny) in path_set or not is_valid_move(x, y, nx, ny): | |
options = [(i, dirs[i]) for i in turns(chosen_dir) | |
if is_valid_move(x, y, x+dirs[i][0], y+dirs[i][1]) and (x+dirs[i][0], y+dirs[i][1]) not in path_set] | |
if not options: break | |
chosen_dir, d, straight = random.choice(options)[0], random.choice(options)[1], 0 | |
continue | |
# Add point and consider turning | |
x, y = nx, ny | |
extended_path.append((x, y)) | |
path_set.add((x, y)) | |
straight += 1 | |
if straight >= min_s and random.random() < 0.2: | |
options = [(i, dirs[i]) for i in turns(chosen_dir) | |
if i != chosen_dir and is_valid_move(x, y, x+dirs[i][0], y+dirs[i][1]) | |
and (x+dirs[i][0], y+dirs[i][1]) not in path_set] | |
if options: chosen_dir, d, straight = random.choice(options)[0], random.choice(options)[1], 0 | |
# Create trace without starting via (from chip) | |
if len(extended_path) >= min_len and trace(extended_path, count, start_via=False, end_via=True): | |
count, chip_traces_created, gen_stats['phase0_traces'] = count + 1, chip_traces_created + 1, gen_stats['phase0_traces'] + 1 | |
if count % 5 == 0: print(f"Created {count} traces (chip-emanating: {chip_traces_created}/{desired_chip_traces})..."); bpy.context.view_layer.update() | |
gen_stats['phase0_time'] = time.time() - phase0_start | |
# Phase 1: Main random traces | |
phase1_start = time.time() | |
main_target, tries = max(count, int(max_traces * (1 - chip_emanating_trace_percentage) * 0.7)), 0 | |
# Generate random traces until target reached | |
while count < main_target and tries < 2000 and time.time() - start < timeout: | |
tries += 1 | |
empty = [(x, y) for (x, y), val in grid.items() if val == -1] | |
if not empty: break | |
# Generate path from random empty cell | |
start_pos = random.choice(empty) | |
adjusted_max = min(max_len, max(min_len + 2, int(min(w, h) * 0.2))) | |
path = generate_path(*start_pos, max_length=adjusted_max) | |
if path and trace(path, count): | |
count, gen_stats['phase1_traces'] = count + 1, gen_stats['phase1_traces'] + 1 | |
if count % 10 == 0: print(f"Created {count} traces..."); bpy.context.view_layer.update() | |
gen_stats['phase1_time'] = time.time() - phase1_start | |
# Phase 2: Gap filling with sectors | |
phase2_start = time.time() | |
if time.time() - start < timeout and count < max_traces and use_sector_based_filling: | |
sector_size = max(3, min(w, h) // 5) | |
# Create and shuffle sectors | |
sectors = [[(x, y) for x in range(sx, min(sx + sector_size, w)) for y in range(sy, min(sy + sector_size, h)) | |
if (x, y) in grid and grid[(x, y)] == -1] | |
for sx in range(0, w, sector_size) for sy in range(0, h, sector_size) | |
if any(grid.get((x, y), -999) == -1 for x in range(sx, min(sx + sector_size, w)) | |
for y in range(sy, min(sy + sector_size, h)))] | |
random.shuffle(sectors) | |
# Process each sector | |
for sector in sectors: | |
if time.time() - start > timeout or count >= max_traces: break | |
tries, fills = 0, 0 | |
random.shuffle(sector) | |
# Fill each sector with traces | |
while sector and tries < 50 and count < max_traces and time.time() - start < timeout: | |
tries += 1 | |
start_pos = sector.pop(0) | |
if grid.get(start_pos, -999) != -1: continue | |
# Find candidates and create paths | |
sx, sy, radius = start_pos[0], start_pos[1], min(sector_size, 4) | |
candidates = [((rx, ry), abs(rx - sx) + abs(ry - sy)) | |
for rx in range(max(0, sx - radius), min(w, sx + radius + 1)) | |
for ry in range(max(0, sy - radius), min(h, sy + radius + 1)) | |
if grid.get((rx, ry), -999) == -1 and (rx, ry) != (sx, sy) | |
and min_len - 1 <= abs(rx - sx) + abs(ry - sy) <= max_len - 1] | |
if not candidates: continue | |
candidates.sort(key=lambda x: x[1]) | |
# Try top candidates | |
for target_pos, _ in candidates[:3]: | |
path = generate_path(sx, sy, *target_pos, max_length=min(max_len, max(min_len, int(min(w, h) * 0.15)))) | |
if path and trace(path, count): | |
count, gen_stats['phase2_traces'], fills = count + 1, gen_stats['phase2_traces'] + 1, fills + 1 | |
sector = [pos for pos in sector if pos not in path] | |
break | |
if fills >= 3: gen_stats['sectors_filled'] += 1; break | |
gen_stats['phase2_time'] = time.time() - phase2_start | |
return gen_stats | |
# Create traces with all phases | |
trace_stats = generate_traces() | |
# Add light with one compact function | |
def add_light(type, location, energy, color, size=None, shadow_soft_size=None): | |
bpy.ops.object.light_add(type=type, radius=1, location=location) | |
light = bpy.context.active_object.data | |
light.energy, light.color = energy, color | |
if size is not None: light.size = size | |
if shadow_soft_size is not None: light.shadow_soft_size = shadow_soft_size | |
return bpy.context.active_object | |
# Setup camera and lighting in one block | |
bpy.ops.object.camera_add(location=(board_width/2, -board_height/2, board_height*1.1), rotation=(math.radians(40), 0, 0)) | |
camera = bpy.context.active_object; bpy.context.scene.camera = camera | |
# Create strategic lighting rig | |
main_light = add_light('AREA', (board_width/2, board_height/2, board_height*1.5), 0.6, (0.15, 0.2, 0.25), size=max(board_width, board_height) * 3) | |
rim_light = add_light('POINT', (board_width*0.3, board_height*0.3, board_height*0.8), 3.0, (0.1, 0.2, 0.4), shadow_soft_size=3.0) | |
key_light = add_light('SPOT', (board_width*0.6, board_height*0.3, board_height*1.3), 5.0, (1.0, 1.0, 1.0), shadow_soft_size=1.0) | |
key_light.data.spot_size, key_light.data.spot_blend = math.radians(30), 0.3 | |
edge_light = add_light('POINT', (board_width*0.5, board_height*0.5, -board_height*0.2), 0.8, (0.05, 0.1, 0.3), shadow_soft_size=10.0) | |
# Configure render settings | |
world = bpy.context.scene.world or bpy.data.worlds.new("World") | |
bpy.context.scene.world, world.use_nodes, world.color = world, False, (0, 0, 0) | |
scene = bpy.context.scene; scene.render.engine = 'CYCLES' | |
scene.cycles.samples, scene.cycles.film_exposure = render_samples, 1.2 | |
# Calculate statistics and return | |
occupied, coverage, trace_density = sum(1 for val in grid.values() if val != -1), (sum(1 for val in grid.values() if val != -1) / (w * h)) * 100, count / (w * h - chip_w * chip_h) * 100 | |
# Return comprehensive statistics | |
stats = { | |
'traces': count, 'time': time.time() - start, 'coverage': coverage, 'density': trace_density, | |
'phase0_traces': trace_stats['phase0_traces'], 'phase1_traces': trace_stats['phase1_traces'], 'phase2_traces': trace_stats['phase2_traces'], | |
'phase0_time': trace_stats['phase0_time'], 'phase1_time': trace_stats['phase1_time'], 'phase2_time': trace_stats['phase2_time'], | |
'sectors_filled': trace_stats['sectors_filled'], 'chip_connected': trace_stats['phase0_traces'] > 0 | |
} | |
return f"Circuit: {count} traces in {stats['time']:.1f}s. Coverage: {coverage:.1f}%. Density: {trace_density:.1f}%. " \ | |
f"Chip Traces: {stats['phase0_traces']} in {stats['phase0_time']:.1f}s. " \ | |
f"Phase 1: {stats['phase1_traces']} traces in {stats['phase1_time']:.1f}s. " \ | |
f"Phase 2: {stats['phase2_traces']} traces in {stats['phase2_time']:.1f}s." | |
# Execute with default parameters | |
create_circuit_pattern() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment