879 lines
38 KiB
Python
879 lines
38 KiB
Python
"""Build an Aetheria Bloom habitat study scene.
|
|
|
|
This is a hybrid authoring test:
|
|
|
|
- Python owns the lore-derived scene brief, tables, materials, labels, camera,
|
|
and Blender object orchestration.
|
|
- Geometry Script emits reusable inspectable node groups for procedural rails.
|
|
- bpy builds the cutaway habitat mesh and render evidence.
|
|
"""
|
|
|
|
import math as pymath
|
|
from pathlib import Path
|
|
|
|
try:
|
|
from tools.geometry_script_loader import load_repo_geometry_script
|
|
except ModuleNotFoundError:
|
|
import sys
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
|
|
from tools.geometry_script_loader import load_repo_geometry_script
|
|
|
|
load_repo_geometry_script()
|
|
|
|
from geometry_script import * # noqa: F403 - Geometry Script exposes node functions as DSL globals.
|
|
|
|
|
|
TAU = pymath.tau
|
|
AXIS_LENGTH = 18.0
|
|
CUT_START = pymath.radians(-132.0)
|
|
CUT_END = pymath.radians(132.0)
|
|
FULL_START = -pymath.pi
|
|
FULL_END = pymath.pi
|
|
INNER_RADIUS = 5.0
|
|
|
|
|
|
@tree("VG Bloom Light Spine")
|
|
def vg_bloom_light_spine(
|
|
length: Float = 18.0,
|
|
helix_radius: Float = 0.18,
|
|
turns: Float = 7.5,
|
|
thickness: Float = 0.035,
|
|
):
|
|
"""A thin procedural light/distribution rail around the axial spire."""
|
|
|
|
rail = curve_line(mode=CurveLine.Mode.POINTS, start=(-9.0, 0.0, 0.0), end=(9.0, 0.0, 0.0))
|
|
samples = resample_curve(keep_last_segment=True, curve=rail, mode="Count", count=420, length=0.1)
|
|
point_count = domain_size(component="CURVE", geometry=samples).point_count
|
|
t = math(operation=Math.Operation.DIVIDE, value=(index(), point_count))
|
|
theta = math(operation=Math.Operation.MULTIPLY, value=(t, math(operation=Math.Operation.MULTIPLY, value=(turns, TAU))))
|
|
x = math(operation=Math.Operation.SUBTRACT, value=(math(operation=Math.Operation.MULTIPLY, value=(t, length)), math(operation=Math.Operation.DIVIDE, value=(length, 2.0))))
|
|
y = math(operation=Math.Operation.MULTIPLY, value=(math(operation=Math.Operation.SINE, value=theta), helix_radius))
|
|
z = math(operation=Math.Operation.MULTIPLY, value=(math(operation=Math.Operation.COSINE, value=theta), helix_radius))
|
|
positioned = set_position(geometry=samples, position=combine_xyz(x=x, y=y, z=z), offset=(0.0, 0.0, 0.0))
|
|
profile = curve_circle(mode=CurveCircle.Mode.RADIUS, resolution=8, radius=thickness)
|
|
return {"Geometry": curve_to_mesh(curve=positioned, profile_curve=profile, fill_caps=True)}
|
|
|
|
|
|
def mat(name, color, emission=False, strength=0.0, alpha=1.0):
|
|
import bpy
|
|
|
|
material = bpy.data.materials.new(name)
|
|
material.diffuse_color = (color[0], color[1], color[2], alpha)
|
|
material.use_nodes = True
|
|
bsdf = material.node_tree.nodes.get("Principled BSDF")
|
|
if bsdf:
|
|
if emission:
|
|
bsdf.inputs["Emission Color"].default_value = (color[0], color[1], color[2], 1.0)
|
|
bsdf.inputs["Emission Strength"].default_value = strength
|
|
bsdf.inputs["Base Color"].default_value = (color[0], color[1], color[2], alpha)
|
|
bsdf.inputs["Roughness"].default_value = 0.62
|
|
bsdf.inputs["Alpha"].default_value = alpha
|
|
if alpha < 1.0:
|
|
material.blend_method = "BLEND"
|
|
material.use_screen_refraction = True
|
|
material.show_transparent_back = True
|
|
return material
|
|
|
|
|
|
def clear_scene():
|
|
import bpy
|
|
|
|
bpy.ops.object.select_all(action="SELECT")
|
|
bpy.ops.object.delete()
|
|
|
|
|
|
def radial(angle: float):
|
|
return (0.0, pymath.cos(angle), pymath.sin(angle))
|
|
|
|
|
|
def tangent(angle: float):
|
|
return (0.0, -pymath.sin(angle), pymath.cos(angle))
|
|
|
|
|
|
def add_shell_segment(name, radius, half_length, thickness, material, angle_steps=144, x_steps=8, start_angle=FULL_START, end_angle=FULL_END):
|
|
import bpy
|
|
|
|
verts = []
|
|
faces = []
|
|
for ix in range(x_steps + 1):
|
|
x = -half_length + (2.0 * half_length * ix / x_steps)
|
|
for ia in range(angle_steps + 1):
|
|
a = start_angle + (end_angle - start_angle) * ia / angle_steps
|
|
verts.append((x, radius * pymath.cos(a), radius * pymath.sin(a)))
|
|
for ix in range(x_steps):
|
|
for ia in range(angle_steps):
|
|
row = angle_steps + 1
|
|
faces.append((ix * row + ia, (ix + 1) * row + ia, (ix + 1) * row + ia + 1, ix * row + ia + 1))
|
|
mesh = bpy.data.meshes.new(name + "Mesh")
|
|
mesh.from_pydata(verts, [], faces)
|
|
mesh.update()
|
|
obj = bpy.data.objects.new(name, mesh)
|
|
bpy.context.collection.objects.link(obj)
|
|
obj.data.materials.append(material)
|
|
obj.show_transparent = True
|
|
solid = obj.modifiers.new("visible thickness", "SOLIDIFY")
|
|
solid.thickness = thickness
|
|
solid.offset = 0.0
|
|
return obj
|
|
|
|
|
|
def surface_point(x, angle, radius=INNER_RADIUS, lift=-0.04):
|
|
r = radius + lift
|
|
return (x, r * pymath.cos(angle), r * pymath.sin(angle))
|
|
|
|
|
|
def add_surface_patch(name, x0, x1, a0, a1, radius, material, lift=-0.045, angle_steps=8, x_steps=3):
|
|
import bpy
|
|
|
|
verts = []
|
|
faces = []
|
|
for ix in range(x_steps + 1):
|
|
x = x0 + (x1 - x0) * ix / x_steps
|
|
for ia in range(angle_steps + 1):
|
|
a = a0 + (a1 - a0) * ia / angle_steps
|
|
verts.append(surface_point(x, a, radius, lift=lift))
|
|
row = angle_steps + 1
|
|
for ix in range(x_steps):
|
|
for ia in range(angle_steps):
|
|
faces.append((ix * row + ia, (ix + 1) * row + ia, (ix + 1) * row + ia + 1, ix * row + ia + 1))
|
|
mesh = bpy.data.meshes.new(name + "Mesh")
|
|
mesh.from_pydata(verts, [], faces)
|
|
mesh.update()
|
|
obj = bpy.data.objects.new(name, mesh)
|
|
bpy.context.collection.objects.link(obj)
|
|
obj.data.materials.append(material)
|
|
return obj
|
|
|
|
|
|
def add_surface_curve(name, samples, radius, bevel, material, lift=-0.11):
|
|
points = [surface_point(x, angle, radius, lift=lift) for x, angle in samples]
|
|
return add_curve_polyline(name, points, bevel, material)
|
|
|
|
|
|
def add_surface_box(name, x, angle, material, size=(0.22, 0.12, 0.08), radial_lift=-0.18):
|
|
r = radial(angle)
|
|
t = tangent(angle)
|
|
loc = surface_point(x, angle, INNER_RADIUS, lift=radial_lift)
|
|
return add_box(name, loc, axes=((1, 0, 0), t, r), size=size, material=material)
|
|
|
|
|
|
def add_surface_block_field(name, x_values, angles, material, size, radial_lift=-0.22, jitter=0.0):
|
|
for ix, x in enumerate(x_values):
|
|
for ia, angle in enumerate(angles):
|
|
if jitter:
|
|
x_offset = ((ix * 17 + ia * 7) % 9 - 4) * jitter
|
|
angle_offset = ((ix * 11 + ia * 5) % 7 - 3) * jitter * 0.05
|
|
else:
|
|
x_offset = 0.0
|
|
angle_offset = 0.0
|
|
add_surface_box(
|
|
f"{name}_{ix:02d}_{ia:02d}",
|
|
x + x_offset,
|
|
angle + angle_offset,
|
|
material,
|
|
size=size,
|
|
radial_lift=radial_lift,
|
|
)
|
|
|
|
|
|
def append_box_parts(verts, faces, loc, axes, size):
|
|
from mathutils import Vector
|
|
|
|
origin = Vector(loc)
|
|
ax = [Vector(axis).normalized() for axis in axes]
|
|
sx, sy, sz = (value * 0.5 for value in size)
|
|
base = len(verts)
|
|
for dx, dy, dz in [
|
|
(-sx, -sy, -sz),
|
|
(sx, -sy, -sz),
|
|
(sx, sy, -sz),
|
|
(-sx, sy, -sz),
|
|
(-sx, -sy, sz),
|
|
(sx, -sy, sz),
|
|
(sx, sy, sz),
|
|
(-sx, sy, sz),
|
|
]:
|
|
v = origin + ax[0] * dx + ax[1] * dy + ax[2] * dz
|
|
verts.append((v.x, v.y, v.z))
|
|
faces.extend(
|
|
[
|
|
(base + 0, base + 1, base + 2, base + 3),
|
|
(base + 4, base + 7, base + 6, base + 5),
|
|
(base + 0, base + 4, base + 5, base + 1),
|
|
(base + 1, base + 5, base + 6, base + 2),
|
|
(base + 2, base + 6, base + 7, base + 3),
|
|
(base + 3, base + 7, base + 4, base + 0),
|
|
]
|
|
)
|
|
|
|
|
|
def add_mesh_parts(name, verts, faces, material):
|
|
import bpy
|
|
|
|
mesh = bpy.data.meshes.new(name + "Mesh")
|
|
mesh.from_pydata(verts, [], faces)
|
|
mesh.update()
|
|
obj = bpy.data.objects.new(name, mesh)
|
|
bpy.context.collection.objects.link(obj)
|
|
obj.data.materials.append(material)
|
|
return obj
|
|
|
|
|
|
def add_multi_polyline_curve(name, lines, bevel, material, resolution=2):
|
|
import bpy
|
|
|
|
curve = bpy.data.curves.new(name + "Curve", "CURVE")
|
|
curve.dimensions = "3D"
|
|
curve.resolution_u = resolution
|
|
curve.bevel_depth = bevel
|
|
curve.bevel_resolution = 2
|
|
for line in lines:
|
|
spline = curve.splines.new("POLY")
|
|
spline.points.add(len(line) - 1)
|
|
for point, co in zip(spline.points, line):
|
|
point.co = (co[0], co[1], co[2], 1.0)
|
|
obj = bpy.data.objects.new(name, curve)
|
|
bpy.context.collection.objects.link(obj)
|
|
obj.data.materials.append(material)
|
|
return obj
|
|
|
|
|
|
def add_surface_stilt(name, x, angle, inner_lift, outer_lift, material, radius=0.009):
|
|
start = surface_point(x, angle, INNER_RADIUS, lift=inner_lift)
|
|
end = surface_point(x, angle, INNER_RADIUS, lift=outer_lift)
|
|
return add_cylinder_between(name, start, end, radius, material, vertices=6)
|
|
|
|
|
|
def add_surface_brace(name, x0, angle0, x1, angle1, lift0, lift1, material, bevel=0.006):
|
|
return add_curve_polyline(
|
|
name,
|
|
[
|
|
surface_point(x0, angle0, INNER_RADIUS, lift=lift0),
|
|
surface_point((x0 + x1) * 0.5, (angle0 + angle1) * 0.5, INNER_RADIUS, lift=(lift0 + lift1) * 0.5 - 0.03),
|
|
surface_point(x1, angle1, INNER_RADIUS, lift=lift1),
|
|
],
|
|
bevel,
|
|
material,
|
|
resolution=3,
|
|
)
|
|
|
|
|
|
def add_favela_fractal_cluster(prefix, x_values, angles, mats):
|
|
shack_verts, shack_faces = [], []
|
|
roof_verts, roof_faces = [], []
|
|
brace_lines = []
|
|
for ix, x in enumerate(x_values):
|
|
for ia, angle in enumerate(angles):
|
|
lean = ((ix * 5 + ia * 3) % 5 - 2) * 0.018
|
|
root = f"{prefix}_{ix:02d}_{ia:02d}"
|
|
axes = ((1, 0, 0), tangent(angle + lean), radial(angle + lean))
|
|
append_box_parts(shack_verts, shack_faces, surface_point(x, angle + lean, INNER_RADIUS, lift=-0.38), axes, (0.2, 0.12, 0.24))
|
|
for level in range(2):
|
|
lift = -0.55 - level * 0.14
|
|
width = 0.13 - level * 0.015
|
|
for wing in (-1, 1):
|
|
dx = wing * (0.09 + 0.035 * level)
|
|
da = wing * (0.018 + 0.012 * level) + lean
|
|
local_axes = ((1, 0, 0), tangent(angle + da), radial(angle + da))
|
|
append_box_parts(
|
|
shack_verts,
|
|
shack_faces,
|
|
surface_point(x + dx, angle + da, INNER_RADIUS, lift=lift),
|
|
local_axes,
|
|
(width, 0.075, 0.11),
|
|
)
|
|
roof_axes = ((1, 0, 0), tangent(angle + da + 0.006 * wing), radial(angle + da + 0.006 * wing))
|
|
append_box_parts(
|
|
roof_verts,
|
|
roof_faces,
|
|
surface_point(x + dx + 0.012 * wing, angle + da + 0.006 * wing, INNER_RADIUS, lift=lift - 0.07),
|
|
roof_axes,
|
|
(width * 1.22, 0.095, 0.018),
|
|
)
|
|
brace_lines.append(
|
|
[
|
|
surface_point(x + dx - width * 0.25, angle + da - 0.006, INNER_RADIUS, lift=lift + 0.05),
|
|
surface_point(x + dx - width * 0.25, angle + da - 0.006, INNER_RADIUS, lift=-0.25),
|
|
]
|
|
)
|
|
brace_lines.append(
|
|
[
|
|
surface_point(x + dx - width * 0.42, angle + da - 0.012, INNER_RADIUS, lift=lift + 0.02),
|
|
surface_point(x + dx, angle + da, INNER_RADIUS, lift=lift - 0.04),
|
|
surface_point(x + dx + width * 0.42, angle + da + 0.012, INNER_RADIUS, lift=lift - 0.09),
|
|
]
|
|
)
|
|
for step in range(2):
|
|
brace_lines.append(
|
|
[
|
|
surface_point(x - 0.18, angle + lean + step * 0.028, INNER_RADIUS, lift=-0.62 - step * 0.08),
|
|
surface_point(x, angle + lean + 0.035 + step * 0.025, INNER_RADIUS, lift=-0.62 - step * 0.08),
|
|
surface_point(x + 0.2, angle + lean + step * 0.02, INNER_RADIUS, lift=-0.62 - step * 0.08),
|
|
]
|
|
)
|
|
add_mesh_parts(prefix + "_shack_mesh", shack_verts, shack_faces, mats["favela"])
|
|
add_mesh_parts(prefix + "_roof_mesh", roof_verts, roof_faces, mats["shack_roof"])
|
|
add_multi_polyline_curve(prefix + "_brace_and_catwalks", brace_lines, 0.005, mats["brace"], resolution=2)
|
|
|
|
|
|
def add_town_detail_cluster(prefix, x_values, angles, material, road_material):
|
|
verts, faces = [], []
|
|
lanes = []
|
|
for ix, x in enumerate(x_values):
|
|
for ia, angle in enumerate(angles):
|
|
root = f"{prefix}_{ix:02d}_{ia:02d}"
|
|
for lane in (-1, 0, 1):
|
|
local_angle = angle + lane * 0.024
|
|
lanes.append(
|
|
[
|
|
surface_point(x - 0.32, local_angle - 0.014, INNER_RADIUS, lift=-0.37),
|
|
surface_point(x, local_angle + 0.008, INNER_RADIUS, lift=-0.37),
|
|
surface_point(x + 0.32, local_angle - 0.006, INNER_RADIUS, lift=-0.37),
|
|
]
|
|
)
|
|
for b in range(3):
|
|
dx = -0.22 + b * 0.22 + ((ix + ia + b) % 3 - 1) * 0.018
|
|
da = lane * 0.026 + ((ix * 7 + ia * 5 + b) % 5 - 2) * 0.004
|
|
height = 0.12 + 0.035 * ((ix + ia + b) % 4)
|
|
append_box_parts(
|
|
verts,
|
|
faces,
|
|
surface_point(x + dx, angle + da, INNER_RADIUS, lift=-0.3 - height * 0.5),
|
|
((1, 0, 0), tangent(angle + da), radial(angle + da)),
|
|
(0.12, 0.07, height),
|
|
)
|
|
add_mesh_parts(prefix + "_nested_house_mesh", verts, faces, material)
|
|
add_multi_polyline_curve(prefix + "_lane_network", lanes, 0.006, road_material, resolution=2)
|
|
|
|
|
|
def add_spoke_plaza_detail(prefix, x, angle, mats, seed):
|
|
add_surface_curve(
|
|
f"{prefix}_plaza_ring",
|
|
[(x + 0.34 * pymath.cos(t), angle + 0.065 * pymath.sin(t)) for t in [TAU * i / 28 for i in range(29)]],
|
|
INNER_RADIUS,
|
|
0.01,
|
|
mats["road"],
|
|
lift=-0.42,
|
|
)
|
|
for i in range(10):
|
|
t = TAU * i / 10 + seed * 0.13
|
|
dx = 0.28 * pymath.cos(t)
|
|
da = 0.052 * pymath.sin(t)
|
|
add_surface_box(
|
|
f"{prefix}_kiosk_{i:02d}",
|
|
x + dx,
|
|
angle + da,
|
|
mats["luxury"],
|
|
size=(0.09, 0.055, 0.1 + 0.025 * (i % 3)),
|
|
radial_lift=-0.37,
|
|
)
|
|
for i, spoke_da in enumerate((-0.11, -0.055, 0.055, 0.11)):
|
|
add_surface_curve(
|
|
f"{prefix}_feeder_walk_{i:02d}",
|
|
[(x - 0.45, angle + spoke_da), (x, angle + spoke_da * 0.3), (x + 0.45, angle - spoke_da * 0.6)],
|
|
INNER_RADIUS,
|
|
0.008,
|
|
mats["pressure_line"],
|
|
lift=-0.43,
|
|
)
|
|
|
|
|
|
def add_rotating_spoke(name, x, angle, inner_radius, outer_radius, material, radius=0.045, vertices=10):
|
|
r = radial(angle)
|
|
return add_cylinder_between(
|
|
name,
|
|
(x, r[1] * inner_radius, r[2] * inner_radius),
|
|
(x, r[1] * outer_radius, r[2] * outer_radius),
|
|
radius,
|
|
material,
|
|
vertices=vertices,
|
|
)
|
|
|
|
|
|
def add_frame_transfer_artery(name, x, angle, start_radius, end_radius, material, sweep=0.42, bevel=0.025):
|
|
samples = []
|
|
for i in range(9):
|
|
t = i / 8
|
|
a = angle + (t - 0.5) * sweep
|
|
radius = start_radius + (end_radius - start_radius) * t
|
|
samples.append((x + (t - 0.5) * 0.65, radius * pymath.cos(a), radius * pymath.sin(a)))
|
|
return add_curve_polyline(name, samples, bevel, material)
|
|
|
|
|
|
def cyl_point(x, angle, radius):
|
|
return (x, radius * pymath.cos(angle), radius * pymath.sin(angle))
|
|
|
|
|
|
def smoothstep(t):
|
|
return t * t * (3.0 - 2.0 * t)
|
|
|
|
|
|
def add_spiral_spoke_loop(name, x, angle, start_radius, end_radius, material, bevel=0.04, cargo=False):
|
|
"""Shared cylinder frame: x is axial, angle is shell azimuth, radius is spin radius."""
|
|
samples = []
|
|
handed = 1.0 if pymath.sin(angle * 2.0 + x * 0.37) >= 0 else -1.0
|
|
tangent_bias = 0.68 if cargo else 0.42
|
|
loop_gain = 0.58 if cargo else 0.34
|
|
for i in range(22):
|
|
u = i / 21
|
|
e = smoothstep(u)
|
|
radius = start_radius + (end_radius - start_radius) * e
|
|
a = angle + handed * (tangent_bias * (1.0 - u) + loop_gain * pymath.sin(u * pymath.pi))
|
|
x2 = x + handed * 0.62 * pymath.sin(u * pymath.pi) + 0.15 * pymath.sin(u * TAU * 2.0)
|
|
samples.append(cyl_point(x2, a, radius))
|
|
return add_curve_polyline(name, samples, bevel, material, resolution=4)
|
|
|
|
|
|
def add_endcap_face(name, x, radius, material, segments=144):
|
|
import bpy
|
|
|
|
mesh = bpy.data.meshes.new(name + "Mesh")
|
|
verts = [(x, 0.0, 0.0)]
|
|
verts.extend(cyl_point(x, FULL_START + TAU * i / segments, radius) for i in range(segments))
|
|
faces = [(0, i + 1, 1 + ((i + 1) % segments)) for i in range(segments)]
|
|
mesh.from_pydata(verts, [], faces)
|
|
mesh.update()
|
|
obj = bpy.data.objects.new(name, mesh)
|
|
bpy.context.collection.objects.link(obj)
|
|
obj.data.materials.append(material)
|
|
return obj
|
|
|
|
|
|
def add_endcap_ring(name, x, radius, material, minor_radius=0.025):
|
|
import bpy
|
|
|
|
bpy.ops.mesh.primitive_torus_add(
|
|
major_radius=radius,
|
|
minor_radius=minor_radius,
|
|
major_segments=128,
|
|
minor_segments=8,
|
|
location=(x, 0, 0),
|
|
)
|
|
obj = bpy.context.object
|
|
obj.name = name
|
|
obj.data.name = name + "Mesh"
|
|
obj.rotation_euler[1] = pymath.radians(90)
|
|
obj.data.materials.append(material)
|
|
return obj
|
|
|
|
|
|
def add_endcap_terrace_blocks(prefix, x, radii, angles, material, height=0.18):
|
|
for ir, radius in enumerate(radii):
|
|
for ia, angle in enumerate(angles):
|
|
loc = cyl_point(x, angle, radius)
|
|
add_box(
|
|
f"{prefix}_{ir:02d}_{ia:02d}",
|
|
loc,
|
|
axes=((1, 0, 0), tangent(angle), radial(angle)),
|
|
size=(0.09, 0.16, height),
|
|
material=material,
|
|
)
|
|
|
|
|
|
def add_cylinder_between(name, start, end, radius, material, vertices=16):
|
|
import bpy
|
|
from mathutils import Vector
|
|
|
|
start_v = Vector(start)
|
|
end_v = Vector(end)
|
|
mid = (start_v + end_v) / 2.0
|
|
direction = end_v - start_v
|
|
length = direction.length
|
|
bpy.ops.mesh.primitive_cylinder_add(vertices=vertices, radius=radius, depth=length, location=mid)
|
|
obj = bpy.context.object
|
|
obj.name = name
|
|
obj.data.name = name + "Mesh"
|
|
obj.rotation_euler = direction.to_track_quat("Z", "Y").to_euler()
|
|
obj.data.materials.append(material)
|
|
return obj
|
|
|
|
|
|
def add_curve_polyline(name, points, bevel, material, resolution=2):
|
|
import bpy
|
|
|
|
curve = bpy.data.curves.new(name + "Curve", "CURVE")
|
|
curve.dimensions = "3D"
|
|
curve.resolution_u = resolution
|
|
curve.bevel_depth = bevel
|
|
curve.bevel_resolution = 4
|
|
spline = curve.splines.new("POLY")
|
|
spline.points.add(len(points) - 1)
|
|
for point, co in zip(spline.points, points):
|
|
point.co = (co[0], co[1], co[2], 1.0)
|
|
obj = bpy.data.objects.new(name, curve)
|
|
bpy.context.collection.objects.link(obj)
|
|
obj.data.materials.append(material)
|
|
return obj
|
|
|
|
|
|
def add_box(name, loc, axes, size, material):
|
|
import bpy
|
|
from mathutils import Matrix, Vector
|
|
|
|
bpy.ops.mesh.primitive_cube_add(size=1.0, location=loc)
|
|
obj = bpy.context.object
|
|
obj.name = name
|
|
obj.dimensions = size
|
|
matrix = Matrix.Identity(4)
|
|
matrix.col[0].xyz = Vector(axes[0]).normalized()
|
|
matrix.col[1].xyz = Vector(axes[1]).normalized()
|
|
matrix.col[2].xyz = Vector(axes[2]).normalized()
|
|
obj.rotation_euler = matrix.to_euler()
|
|
obj.data.materials.append(material)
|
|
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
|
|
return obj
|
|
|
|
|
|
def add_label(name, text, loc, size, material):
|
|
import bpy
|
|
|
|
bpy.ops.object.text_add(location=loc, rotation=(pymath.radians(70), 0.0, pymath.radians(0)))
|
|
obj = bpy.context.object
|
|
obj.name = name
|
|
obj.data.body = text
|
|
obj.data.align_x = "CENTER"
|
|
obj.data.align_y = "CENTER"
|
|
obj.data.size = size
|
|
obj.data.materials.append(material)
|
|
return obj
|
|
|
|
|
|
def add_gn_light_spine(material):
|
|
import bpy
|
|
|
|
group = bpy.data.node_groups.get("VG Bloom Light Spine")
|
|
mesh = bpy.data.meshes.new("BloomLightSpineCarrierMesh")
|
|
obj = bpy.data.objects.new("GN_Light_Spine_Modifier_Carrier", mesh)
|
|
bpy.context.collection.objects.link(obj)
|
|
mod = obj.modifiers.new("VG Bloom Light Spine", "NODES")
|
|
mod.node_group = group
|
|
obj.data.materials.append(material)
|
|
return obj
|
|
|
|
|
|
def aim_camera(obj, target):
|
|
from mathutils import Vector
|
|
|
|
direction = Vector(target) - obj.location
|
|
obj.rotation_euler = direction.to_track_quat("-Z", "Y").to_euler()
|
|
|
|
|
|
def build_scene():
|
|
import bpy
|
|
|
|
clear_scene()
|
|
|
|
mats = {
|
|
"inner": mat("civic inner surface - open commons", (0.28, 0.30, 0.32), alpha=0.78),
|
|
"utility": mat("utility mat amber", (0.95, 0.57, 0.18), alpha=0.42),
|
|
"pressure": mat("pressure laminate blue", (0.18, 0.45, 0.86), alpha=0.2),
|
|
"shield": mat("aggregate shielding graphite", (0.08, 0.075, 0.07), alpha=0.38),
|
|
"hub": mat("despun hub white", (0.82, 0.86, 0.88), alpha=0.88),
|
|
"endcap": mat("layered endcap pressure face", (0.62, 0.66, 0.7), alpha=0.62),
|
|
"spire_sheath": mat("spun spire sheath", (0.38, 0.42, 0.46), alpha=0.34),
|
|
"light": mat("light spine gold", (1.0, 0.78, 0.25), emission=True, strength=1.6),
|
|
"farm": mat("TCS Root farms", (0.15, 0.55, 0.24), alpha=0.92),
|
|
"district": mat("surface districts", (0.55, 0.58, 0.62), alpha=0.95),
|
|
"favela": mat("hubcap terrace slums", (0.74, 0.48, 0.26), alpha=0.98),
|
|
"shack_roof": mat("patched favela sheet metal", (0.46, 0.39, 0.32), alpha=0.98),
|
|
"brace": mat("rickety brace dark steel", (0.12, 0.10, 0.085), alpha=0.98),
|
|
"luxury": mat("luxury spoke districts", (0.82, 0.78, 0.62), alpha=0.98),
|
|
"industrial": mat("industrial works", (0.32, 0.34, 0.36), alpha=0.98),
|
|
"suburban": mat("suburban mixed belt", (0.5, 0.64, 0.5), alpha=0.96),
|
|
"beach": mat("beach rim", (0.86, 0.76, 0.48), alpha=0.96),
|
|
"water": mat("cylindrical sea", (0.05, 0.28, 0.55), alpha=0.82),
|
|
"forest": mat("forest canopy", (0.04, 0.32, 0.1), alpha=0.92),
|
|
"road": mat("road graph", (0.72, 0.66, 0.52), alpha=0.96),
|
|
"city": mat("city clusters", (0.62, 0.64, 0.67), alpha=0.98),
|
|
"cloud": mat("interior cloud layer", (0.8, 0.86, 0.9), alpha=0.24),
|
|
"air": mat("air return cyan", (0.1, 0.85, 1.0), emission=True, strength=0.25),
|
|
"coolant": mat("coolant blue", (0.08, 0.25, 1.0), emission=True, strength=0.2),
|
|
"condensate": mat("condensate green", (0.15, 0.95, 0.55), emission=True, strength=0.15),
|
|
"pressure_line": mat("pressure amber", (1.0, 0.65, 0.08), emission=True, strength=0.35),
|
|
"hazard": mat("emergency red", (1.0, 0.12, 0.07), emission=True, strength=0.45),
|
|
"label": mat("label white", (0.92, 0.95, 1.0), emission=True, strength=0.4),
|
|
"glass": mat("operations glass", (0.45, 0.8, 1.0), alpha=0.28),
|
|
}
|
|
|
|
add_shell_segment("01_open_civic_inner_surface_full_bloom", 5.0, AXIS_LENGTH / 2, 0.045, mats["inner"])
|
|
add_shell_segment("02_utility_mat_service_interlayer_full_bloom", 5.35, AXIS_LENGTH / 2, 0.075, mats["utility"])
|
|
add_shell_segment("03_pressure_structural_shell_full_bloom", 5.75, AXIS_LENGTH / 2, 0.11, mats["pressure"])
|
|
add_shell_segment("04_outer_aggregate_shielding_full_bloom", 6.35, AXIS_LENGTH / 2, 0.28, mats["shield"])
|
|
|
|
add_cylinder_between("despun_center_axis_core", (-10.2, 0, 0), (10.2, 0, 0), 0.28, mats["hub"], vertices=32)
|
|
add_cylinder_between("despun_hub_cap_docking_axis", (-10.85, 0, 0), (-9.25, 0, 0), 0.72, mats["hub"], vertices=48)
|
|
add_cylinder_between("docking_port_on_despun_hub_cap", (-11.55, 0, 0), (-10.75, 0, 0), 0.36, mats["pressure_line"], vertices=32)
|
|
add_cylinder_between("traffic_light_spire_core", (-9.2, 0, 0), (9.2, 0, 0), 0.13, mats["light"], vertices=16)
|
|
add_cylinder_between("spun_spire_sheath_outer_frame", (-8.9, 0, 0), (8.9, 0, 0), 0.82, mats["spire_sheath"], vertices=48)
|
|
add_gn_light_spine(mats["light"])
|
|
|
|
# The Bloom lives in one cylindrical coordinate space. Spokes, endcaps,
|
|
# surface districts, roads, clouds, and transfer arteries all speak x/angle/radius.
|
|
for x, prefix in [(-9.05, "hubward"), (9.05, "capward")]:
|
|
add_endcap_face(f"{prefix}_endcap_pressure_face", x, 5.72, mats["endcap"])
|
|
add_endcap_face(f"{prefix}_endcap_inner_face", x + (0.035 if x < 0 else -0.035), INNER_RADIUS, mats["inner"], segments=128)
|
|
for ir, rr in enumerate((0.82, 1.6, 2.45, 3.25, 4.05, 4.75, 5.35)):
|
|
mat_key = "favela" if x < 0 and ir >= 2 else "beach" if x > 0 and ir >= 4 else "pressure_line"
|
|
add_endcap_ring(f"{prefix}_endcap_terrace_ring_{ir:02d}", x, rr, mats[mat_key], minor_radius=0.018 + ir * 0.002)
|
|
add_endcap_terrace_blocks(
|
|
"hubward_endcap_slum_balcony",
|
|
-9.12,
|
|
[2.1, 2.7, 3.3, 3.9, 4.45],
|
|
[pymath.radians(a) for a in range(-170, 181, 20)],
|
|
mats["favela"],
|
|
height=0.22,
|
|
)
|
|
add_endcap_terrace_blocks(
|
|
"capward_endcap_beach_service",
|
|
9.12,
|
|
[3.5, 4.1, 4.65],
|
|
[pymath.radians(a) for a in range(-160, 181, 40)],
|
|
mats["beach"],
|
|
height=0.1,
|
|
)
|
|
|
|
# Golden-angle spokes fill the cylinder like seeds on a head: each new
|
|
# artery avoids the previous one while staying in the same global frame.
|
|
golden_angle = pymath.pi * (3.0 - pymath.sqrt(5.0))
|
|
spoke_count = 46
|
|
prestige_angles = []
|
|
for i in range(spoke_count):
|
|
t = i / (spoke_count - 1)
|
|
x = -8.25 + 16.5 * t
|
|
a = ((i * golden_angle + 0.48 * pymath.sin(t * TAU * 3.0)) % TAU) - pymath.pi
|
|
cargo = i % 3 == 0
|
|
material = mats["pressure_line"] if cargo else mats["hub"]
|
|
bevel = 0.052 if cargo else 0.036
|
|
name = "spiral_cargo_spoke" if cargo else "spiral_passenger_spoke"
|
|
add_spiral_spoke_loop(f"{name}_{i:02d}", x, a, 0.82, 5.18, material, bevel=bevel, cargo=cargo)
|
|
add_cylinder_between(
|
|
f"spiral_spoke_shell_collar_{i:02d}",
|
|
cyl_point(x - 0.2, a, 5.05),
|
|
cyl_point(x + 0.2, a, 5.05),
|
|
0.12 if cargo else 0.085,
|
|
mats["pressure_line"],
|
|
vertices=18,
|
|
)
|
|
add_frame_transfer_artery(
|
|
f"spun_to_despun_transfer_loop_{i:02d}",
|
|
x,
|
|
a + pymath.pi / 2,
|
|
0.28,
|
|
0.82,
|
|
mats["pressure_line"],
|
|
sweep=0.95 if cargo else 0.62,
|
|
bevel=0.017,
|
|
)
|
|
if i % 5 == 0:
|
|
prestige_angles.append(a)
|
|
for i in range(64):
|
|
t = i / 63
|
|
x = -8.65 + 17.3 * t
|
|
a = ((i * golden_angle * 1.618 + 0.72 * pymath.sin(t * TAU * 2.0)) % TAU) - pymath.pi
|
|
add_spiral_spoke_loop(f"spiral_atmospheric_utility_rib_{i:02d}", x, a, 0.95, 5.55, mats["air"], bevel=0.015, cargo=False)
|
|
|
|
# Whole-cylinder civic gradient. Axial bands move from hub-cap terrace slums
|
|
# through dense and luxury urban districts, mixed suburbs, industrial/farm
|
|
# belts, and finally a beach/sea edge.
|
|
add_surface_patch("hubcap_terrace_slum_band", -8.9, -7.35, FULL_START, FULL_END, INNER_RADIUS, mats["favela"], lift=-0.20, angle_steps=72, x_steps=3)
|
|
add_surface_patch("hyperurban_favela_city_belt", -7.35, -5.6, FULL_START, FULL_END, INNER_RADIUS, mats["city"], lift=-0.18, angle_steps=72, x_steps=3)
|
|
add_surface_patch("prestige_urban_luxury_spoke_belt", -5.6, -2.7, FULL_START, FULL_END, INNER_RADIUS, mats["luxury"], lift=-0.17, angle_steps=72, x_steps=4)
|
|
add_surface_patch("urban_mixed_suburban_industrial_belt", -2.7, 0.9, FULL_START, FULL_END, INNER_RADIUS, mats["suburban"], lift=-0.18, angle_steps=72, x_steps=4)
|
|
add_surface_patch("industrial_agricultural_belt", 0.9, 5.8, FULL_START, FULL_END, INNER_RADIUS, mats["farm"], lift=-0.16, angle_steps=72, x_steps=5)
|
|
add_surface_patch("beach_resort_rim", 5.8, 7.05, FULL_START, FULL_END, INNER_RADIUS, mats["beach"], lift=-0.23, angle_steps=72, x_steps=3)
|
|
add_surface_patch("cylindrical_sea_after_beach", 7.05, 8.85, FULL_START, FULL_END, INNER_RADIUS, mats["water"], lift=-0.25, angle_steps=72, x_steps=3)
|
|
|
|
dense_angles = [pymath.radians(a) for a in range(-165, 180, 15)]
|
|
add_surface_block_field("layered_hubcap_slum_stack", [-8.65, -8.25, -7.85, -7.45], dense_angles, mats["favela"], size=(0.16, 0.08, 0.22), radial_lift=-0.35, jitter=0.035)
|
|
add_surface_block_field("hyperurban_microtower", [-7.0, -6.55, -6.1, -5.7], dense_angles[::2], mats["city"], size=(0.2, 0.1, 0.36), radial_lift=-0.42, jitter=0.03)
|
|
add_favela_fractal_cluster(
|
|
"fractal_wall_favela",
|
|
[-8.78, -8.42, -8.06, -7.7, -7.34, -6.98, -6.62],
|
|
[pymath.radians(a) for a in range(-165, 181, 30)],
|
|
mats,
|
|
)
|
|
add_town_detail_cluster(
|
|
"hyperurban_nested_town",
|
|
[-7.1, -6.65, -6.2, -5.75],
|
|
[pymath.radians(a) for a in range(-150, 181, 45)],
|
|
mats["city"],
|
|
mats["road"],
|
|
)
|
|
for x in [-5.0, -4.2, -3.4]:
|
|
for ia, a in enumerate(prestige_angles[:8]):
|
|
add_surface_box(f"luxury_spoke_plaza_{x:.1f}_{a:.2f}", x, a, mats["luxury"], size=(0.55, 0.34, 0.14), radial_lift=-0.31)
|
|
add_spoke_plaza_detail(f"luxury_spoke_plaza_detail_{x:.1f}_{ia:02d}", x, a, mats, ia)
|
|
for da in (-0.16, 0.16):
|
|
add_surface_box(f"luxury_spoke_tower_{x:.1f}_{a:.2f}_{da:.1f}", x + da, a + da, mats["city"], size=(0.18, 0.1, 0.42), radial_lift=-0.44)
|
|
add_surface_block_field("mixed_suburban_industrial_block", [-2.2, -1.4, -0.6, 0.2], [pymath.radians(a) for a in range(-150, 181, 30)], mats["suburban"], size=(0.42, 0.18, 0.12), radial_lift=-0.27, jitter=0.04)
|
|
add_town_detail_cluster(
|
|
"mixed_belt_nested_town",
|
|
[-2.35, -1.65, -0.95, -0.25, 0.45],
|
|
[pymath.radians(a) for a in range(-150, 181, 60)],
|
|
mats["suburban"],
|
|
mats["road"],
|
|
)
|
|
add_surface_block_field("factory_yard_block", [1.2, 2.0, 2.8, 3.6], [pymath.radians(a) for a in range(-150, 181, 45)], mats["industrial"], size=(0.62, 0.22, 0.16), radial_lift=-0.29, jitter=0.035)
|
|
add_surface_block_field("farm_service_shed", [4.2, 5.0, 5.6], [pymath.radians(a) for a in range(-165, 181, 45)], mats["farm"], size=(0.52, 0.16, 0.08), radial_lift=-0.24, jitter=0.04)
|
|
for i, deg in enumerate(range(-170, 181, 20)):
|
|
a = pymath.radians(deg + (i % 3) * 3)
|
|
add_surface_curve(f"axial_surface_service_lane_{i:02d}", [(-8.7, a), (-5.5, a + 0.08), (-1.4, a - 0.04), (2.9, a + 0.06), (8.6, a - 0.02)], INNER_RADIUS, 0.007, mats["pressure_line"], lift=-0.43)
|
|
for i, x in enumerate([-8.65, -7.25, -5.85, -4.45, -3.05, -1.65, -0.25, 1.15, 2.55, 3.95, 5.35, 6.75, 8.15]):
|
|
add_surface_curve(f"circumferential_market_seam_{i:02d}", [(x, pymath.radians(a)) for a in range(-180, 181, 18)], INNER_RADIUS, 0.006, mats["road"], lift=-0.45)
|
|
|
|
road_specs = [
|
|
[(-8.7, -160), (-7.2, -130), (-5.6, -94), (-3.0, -58), (-1.0, -34), (1.6, -14), (4.4, 8), (6.7, 26), (8.4, 44)],
|
|
[(-8.4, 140), (-6.7, 118), (-4.9, 92), (-2.0, 72), (0.8, 62), (3.2, 74), (5.4, 94), (8.1, 122)],
|
|
[(-8.2, -26), (-6.0, -8), (-3.6, 18), (-1.0, 42), (1.8, 50), (4.2, 38), (6.4, 20), (8.4, -2)],
|
|
[(-8.6, 12), (-6.2, 32), (-4.2, 48), (-1.8, 38), (0.4, 12), (2.8, -22), (5.5, -52), (8.2, -84)],
|
|
]
|
|
for i, spec in enumerate(road_specs):
|
|
add_surface_curve(f"wrapped_road_graph_{i}", [(x, pymath.radians(a)) for x, a in spec], INNER_RADIUS, 0.016, mats["road"], lift=-0.39)
|
|
river_specs = [
|
|
[(-8.6, 82), (-6.5, 68), (-4.6, 42), (-2.2, 22), (0.8, 8), (3.8, -2), (6.1, -12), (8.6, -24)],
|
|
[(-8.2, -112), (-5.6, -92), (-3.0, -72), (-0.2, -58), (2.6, -46), (5.2, -38), (8.4, -34)],
|
|
]
|
|
for i, spec in enumerate(river_specs):
|
|
add_surface_curve(f"wrapped_river_{i}", [(x, pymath.radians(a)) for x, a in spec], INNER_RADIUS, 0.035, mats["water"], lift=-0.34)
|
|
for i, (x, a, sx) in enumerate([(-6.9, -120, 0.8), (-5.2, 75, 0.7), (-2.4, 15, 0.9), (0.9, -54, 0.8), (3.4, 88, 0.9), (5.8, -12, 1.0)]):
|
|
add_surface_patch(f"airflow_cloud_patch_{i}", x - sx, x + sx, pymath.radians(a - 12), pymath.radians(a + 12), 3.25, mats["cloud"], lift=0.0, angle_steps=10, x_steps=3)
|
|
|
|
# Service and heat/air/water flows running below the public surface.
|
|
for deg, material, label in [
|
|
(-112, mats["air"], "inward warm return"),
|
|
(-105, mats["coolant"], "coolant trunk"),
|
|
(-98, mats["condensate"], "condensate recovery"),
|
|
(105, mats["pressure_line"], "pressure reserve"),
|
|
(112, mats["hazard"], "emergency isolation"),
|
|
]:
|
|
a = pymath.radians(deg)
|
|
r = radial(a)
|
|
add_curve_polyline(
|
|
label.replace(" ", "_"),
|
|
[(-8.6, r[1] * 5.42, r[2] * 5.42), (8.6, r[1] * 5.42, r[2] * 5.42)],
|
|
0.035,
|
|
material,
|
|
)
|
|
|
|
# Kappa service ring: kept as one small local maintenance marker, not the point of the scene.
|
|
kappa_x = -2.7
|
|
kappa_angle = pymath.radians(-45)
|
|
kr = radial(kappa_angle)
|
|
kt = tangent(kappa_angle)
|
|
service_center = (kappa_x, kr[1] * 5.22, kr[2] * 5.22)
|
|
add_box("service_ring_kappa_human_gallery", service_center, axes=((1, 0, 0), kt, kr), size=(2.4, 0.34, 0.16), material=mats["utility"])
|
|
add_box(
|
|
"kappa_operations_gallery_badge_route",
|
|
(kappa_x - 0.25, kr[1] * 4.72, kr[2] * 4.72 + 0.28),
|
|
axes=((1, 0, 0), kt, kr),
|
|
size=(1.7, 0.24, 0.18),
|
|
material=mats["glass"],
|
|
)
|
|
add_box(
|
|
"octopoid_support_rig_prep_station",
|
|
(kappa_x + 1.35, kr[1] * 5.05, kr[2] * 5.05 - 0.12),
|
|
axes=((1, 0, 0), kt, kr),
|
|
size=(0.7, 0.32, 0.2),
|
|
material=mats["hazard"],
|
|
)
|
|
|
|
# Crawl throats branch outward into the shell. Seal lungs live beyond them.
|
|
for i, offset in enumerate([-0.62, 0.0, 0.62], start=1):
|
|
base = (kappa_x + offset, kr[1] * 5.36, kr[2] * 5.36)
|
|
end = (kappa_x + offset, kr[1] * 6.14, kr[2] * 6.14)
|
|
add_cylinder_between(f"kappa_crawl_throat_{i}", base, end, 0.055, mats["hazard"], vertices=10)
|
|
bpy.ops.mesh.primitive_uv_sphere_add(segments=24, ring_count=12, radius=0.23, location=(kappa_x + offset, kr[1] * 6.0, kr[2] * 6.0))
|
|
lung = bpy.context.object
|
|
lung.name = f"kappa_seal_lung_{i}"
|
|
lung.scale = (0.8, 1.0, 0.45)
|
|
lung.data.materials.append(mats["pressure_line"])
|
|
|
|
manifold_base = (kappa_x - 0.05, kr[1] * 5.48, kr[2] * 5.48)
|
|
for dz, material, name in [
|
|
(-0.12, mats["air"], "air_return_to_filters"),
|
|
(0.0, mats["coolant"], "coolant_heat_exchanger"),
|
|
(0.12, mats["condensate"], "condensate_drain"),
|
|
]:
|
|
add_curve_polyline(
|
|
"kappa_" + name,
|
|
[
|
|
(manifold_base[0] - 1.0, manifold_base[1], manifold_base[2] + dz),
|
|
(manifold_base[0], manifold_base[1] + kt[1] * 0.55, manifold_base[2] + kt[2] * 0.55 + dz),
|
|
(manifold_base[0] + 1.0, manifold_base[1], manifold_base[2] + dz),
|
|
],
|
|
0.025,
|
|
material,
|
|
)
|
|
|
|
bpy.ops.object.light_add(type="AREA", location=(-1.6, 6.7, -2.0))
|
|
kappa_light = bpy.context.object
|
|
kappa_light.name = "Kappa colored service inspection light"
|
|
kappa_light.data.energy = 360
|
|
kappa_light.data.size = 4.0
|
|
|
|
# Labels are sparse: only the relationships the lore says a blueprint must show.
|
|
add_label("label_inward", "INWARD: open air / light spine / hub", (5.8, 1.2, 1.0), 0.23, mats["label"])
|
|
add_label("label_outward", "OUTWARD: utility mat -> pressure shell -> shielding", (-6.0, -3.8, -4.1), 0.21, mats["label"])
|
|
add_label("label_regions", "mapped regions: sea, plains, cities, fields, forests, roads, rivers", (-3.4, 3.8, 3.5), 0.16, mats["label"])
|
|
add_label("label_kappa", "one service-ring marker among many", (kappa_x, -4.2, -4.0), 0.14, mats["label"])
|
|
add_label("label_spokes", "spoke interfaces: transit, utilities, authority", (1.8, 2.2, 3.2), 0.18, mats["label"])
|
|
|
|
# Lighting and camera.
|
|
bpy.ops.object.light_add(type="AREA", location=(1.5, -6.5, 7.0))
|
|
key = bpy.context.object
|
|
key.name = "large soft inspection light"
|
|
key.data.energy = 650
|
|
key.data.size = 6.5
|
|
bpy.ops.object.camera_add(location=(15.5, -19.0, 12.5))
|
|
bpy.context.scene.camera = bpy.context.object
|
|
bpy.context.object.name = "Camera_Bloom_Cutaway"
|
|
aim_camera(bpy.context.object, (0.0, 0.0, 0.0))
|
|
bpy.context.object.data.type = "ORTHO"
|
|
bpy.context.object.data.ortho_scale = 20.5
|
|
|
|
bpy.ops.object.camera_add(location=(-0.8, 9.5, -7.5))
|
|
detail_camera = bpy.context.object
|
|
detail_camera.name = "Camera_Interior_World"
|
|
aim_camera(detail_camera, (0.0, 0.0, 0.0))
|
|
detail_camera.data.type = "ORTHO"
|
|
detail_camera.data.ortho_scale = 9.0
|
|
|
|
bpy.context.scene.render.engine = "BLENDER_EEVEE"
|
|
if hasattr(bpy.context.scene, "eevee"):
|
|
bpy.context.scene.eevee.taa_render_samples = 24
|
|
bpy.context.scene.render.resolution_x = 1400
|
|
bpy.context.scene.render.resolution_y = 1000
|
|
bpy.context.scene.view_settings.view_transform = "Filmic"
|
|
bpy.context.scene.view_settings.look = "Medium High Contrast"
|
|
|
|
|
|
def main():
|
|
import bpy
|
|
|
|
# Force Geometry Script group creation before scene objects reference it.
|
|
vg_bloom_light_spine()
|
|
build_scene()
|
|
|
|
out_dir = Path("experiments/generated/aetheria_bloom")
|
|
out_dir.mkdir(parents=True, exist_ok=True)
|
|
blend_path = out_dir / "aetheria_bloom_habitat.blend"
|
|
render_path = out_dir / "aetheria_bloom_habitat.png"
|
|
detail_render_path = out_dir / "aetheria_bloom_interior_world.png"
|
|
bpy.ops.wm.save_as_mainfile(filepath=str(blend_path.resolve()))
|
|
bpy.context.scene.camera = bpy.data.objects["Camera_Bloom_Cutaway"]
|
|
bpy.context.scene.render.filepath = str(render_path.resolve())
|
|
bpy.ops.render.render(write_still=True)
|
|
bpy.context.scene.camera = bpy.data.objects["Camera_Interior_World"]
|
|
bpy.context.scene.render.filepath = str(detail_render_path.resolve())
|
|
bpy.ops.render.render(write_still=True)
|
|
print("AETHERIA_BLOOM_BLEND", blend_path.resolve())
|
|
print("AETHERIA_BLOOM_RENDER", render_path.resolve())
|
|
print("AETHERIA_BLOOM_INTERIOR_RENDER", detail_render_path.resolve())
|
|
group = bpy.data.node_groups.get("VG Bloom Light Spine")
|
|
if group:
|
|
print("AETHERIA_BLOOM_GN_GROUP", group.name, len(group.nodes), len(group.links))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|