Last active
October 9, 2024 18:18
-
-
Save brokenpylons/dcc4005fa1264bd7da82cf74d079704a to your computer and use it in GitHub Desktop.
This file contains 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 | |
from typing import Sequence, TypeVar, Generic | |
from bpy_extras import io_utils, node_shader_utils | |
from dataclasses import dataclass | |
import io | |
import mathutils | |
import math | |
lvl = 2 | |
@dataclass | |
class Name: | |
value: any | |
def export(self, indent, f: io.TextIOBase): | |
f.write(f"\"{self.value}\"") | |
@dataclass | |
class Value: | |
value: any | |
def export(self, indent, f: io.TextIOBase): | |
f.write(f"{self.value}") | |
T = TypeVar("T") | |
@dataclass | |
class Values(Generic[T]): | |
values: Sequence[T] | |
def export(self, indent, f: io.TextIOBase): | |
[head, *tail] = self.values | |
head.export(indent, f) | |
for value in tail: | |
f.write(" ") | |
value.export(indent, f) | |
@dataclass | |
class Entry: | |
key: str | |
value: any | |
def export(self, indent, f: io.TextIOBase): | |
f.write(f"{' ' * indent}{self.key} ") | |
self.value.export(indent, f) | |
f.write("\n") | |
@dataclass | |
class Point: | |
x: float | |
y: float | |
def export(self, indent, f: io.TextIOBase): | |
f.write(f"{self.x:.6f} {self.y:.6f}") | |
@dataclass | |
class Vector: | |
x: float | |
y: float | |
z: float | |
def export(self, indent, f: io.TextIOBase): | |
f.write(f"{self.x:.6f} {self.y:.6f} {self.z:.6f}") | |
@dataclass | |
class Triangle: | |
i: int | |
j: int | |
k: int | |
def export(self, indent, f: io.TextIOBase): | |
f.write(f"{self.i:d} {self.j:d} {self.k:d}") | |
@dataclass | |
class Color: | |
r: float | |
g: float | |
b: float | |
def export(self, indent, f: io.TextIOBase): | |
f.write(f"{{ \"sRGB nonlinear\" {self.r:.3f} {self.g:.3f} {self.b:.3f} }}") | |
@dataclass | |
class Objects: | |
objects: list[any] | |
def export(self, indent, f): | |
for x in self.objects: | |
x.export(indent, f) | |
f.write("\n") | |
class Points: | |
def __init__(self): | |
self.vertices: list[Vector] = [] | |
def add(self, v): | |
self.vertices.append(v) | |
def export(self, indent, f: io.TextIOBase): | |
f.write(f"{' ' * indent}points {len(self.vertices)}\n") | |
for v in self.vertices: | |
f.write(' ' * (indent + lvl)) | |
v.export(indent, f) | |
f.write("\n") | |
class Triangles: | |
def __init__(self): | |
self.indices: list[Triangle] = [] | |
def add(self, t): | |
self.indices.append(t) | |
def export(self, indent, f: io.TextIOBase): | |
f.write(f"{' ' * indent}triangles {len(self.indices)}\n") | |
for t in self.indices: | |
f.write(' ' * (indent + lvl)) | |
t.export(indent, f) | |
f.write("\n") | |
class Shaders: | |
def __init__(self): | |
self.names: list[Value] = [] | |
def add(self, name): | |
self.names.append(Value(name)) | |
def export(self, indent, f: io.TextIOBase): | |
f.write(f"{' ' * indent}shaders {len(self.names)}\n") | |
for name in self.names: | |
f.write(' ' * (indent + lvl)) | |
name.export(indent, f) | |
f.write("\n") | |
class FaceShaders: | |
def __init__(self): | |
self.indices: list[Value] = [] | |
def add(self, i): | |
self.indices.append(Value(i)) | |
def export(self, indent, f: io.TextIOBase): | |
f.write(f"{' ' * indent}face_shaders\n") | |
for i in self.indices: | |
f.write(' ' * (indent + lvl)) | |
i.export(indent, f) | |
f.write("\n") | |
class Normals: | |
def __init__(self): | |
self.normals: list[Values[Vector]] = [] | |
def add(self, n1, n2, n3): | |
self.normals.append(Values((n1, n2, n3))) | |
def export(self, indent, f: io.TextIOBase): | |
f.write(f"{' ' * indent}normals facevarying\n") | |
for ns in self.normals: | |
f.write(' ' * (indent + lvl)) | |
ns.export(indent, f) | |
f.write("\n") | |
class UVsNone: | |
def export(self, indent, f: io.TextIOBase): | |
f.write(f"{' ' * indent}uvs none\n") | |
class UVs: | |
def __init__(self): | |
self.uvs: list[Values[Point]] = [] | |
def add(self, p1, p2, p3): | |
self.uvs.append(Values((p1, p2, p3))) | |
def export(self, indent, f: io.TextIOBase): | |
f.write(f"{' ' * indent}uvs facevarying\n") | |
for ps in self.uvs: | |
f.write(' ' * (indent + lvl)) | |
ps.export(indent, f) | |
f.write("\n") | |
class GenericMesh: | |
def __init__(self, name: str, shaders: Shaders, points: Points, triangles: Triangles, normals: Normals, uvs: UVs, face_shaders: FaceShaders): | |
self.name = Entry("name", Name(name)) | |
self.shaders = shaders | |
self.object_type = Entry("type", Value("generic-mesh")) | |
self.points = points | |
self.triangles = triangles | |
self.normals = normals | |
self.uvs = uvs | |
self.face_shaders = face_shaders | |
def export(self, indent, f): | |
f.write(f"{' ' * indent}object {{\n") | |
for x in [self.shaders, self.object_type, self.name, self.points, self.triangles, self.normals, self.uvs, self.face_shaders]: | |
x.export(indent + lvl, f) | |
f.write(f"{' ' * indent}}}\n") | |
class Camera: | |
def __init__(self, eye: Vector, target: Vector, up: Vector, fov: float, aspect: float): | |
self.type = Entry("type", Value("pinhole")) | |
self.eye = Entry("eye", eye) | |
self.target = Entry("target", target) | |
self.up = Entry("up", up) | |
self.fov = Entry("fov", Value(fov)) | |
self.aspect = Entry("aspect", Value(aspect)) | |
def export(self, indent, f): | |
f.write(f"{' ' * indent}camera {{\n") | |
for x in [self.type, self.eye, self.target, self.up, self.fov, self.aspect]: | |
x.export(indent + lvl, f) | |
f.write(f"{' ' * indent}}}\n") | |
class PointLight: | |
def __init__(self, color: Color, power: float, position: Vector): | |
self.type = Entry("type", Value("point")) | |
self.color = Entry("color", color) | |
self.aspect = Entry("power", Value(power)) | |
self.position = Entry("p", position) | |
def export(self, indent, f): | |
f.write(f"{' ' * indent}light {{\n") | |
for x in [self.type, self.color, self.aspect, self.position]: | |
x.export(indent + lvl, f) | |
f.write(f"{' ' * indent}}}\n") | |
class SunLight: | |
def __init__(self, direction: Vector): | |
self.type = Entry("type", Value("sunsky")) | |
self.up = Entry("up", Vector(0, 0, 1)) | |
self.east = Entry("east", Vector(0, 1, 0)) | |
self.direction = Entry("sundir", direction) | |
self.turbidity = Entry("turbidity", Value(4)) | |
self.samples = Entry("samples", Value(64)) | |
def export(self, indent, f): | |
f.write(f"{' ' * indent}light {{\n") | |
for x in [self.type, self.up, self.east, self.direction, self.turbidity, self.samples]: | |
x.export(indent + lvl, f) | |
f.write(f"{' ' * indent}}}\n") | |
class SpotLight: | |
def __init__(self, color: Color, source: Vector, target: Vector, radius: float): | |
self.type = Entry("type", Value("directional")) | |
self.source = Entry("source", source) | |
self.target = Entry("target", target) | |
self.radius = Entry("radius", Value(radius)) | |
self.emit = Entry("emit", color) | |
def export(self, indent, f): | |
f.write(f"{' ' * indent}light {{\n") | |
for x in [self.type, self.source, self.target, self.radius, self.emit]: | |
x.export(indent + lvl, f) | |
f.write(f"{' ' * indent}}}\n") | |
class Shader: | |
def __init__(self, name: str, diffuse: Color, specular: Color, glossy: float): | |
self.name = Entry("name", Value(name)) | |
self.type = Entry("type", Value("uber")) | |
self.diffuse = Entry("diff", diffuse) | |
self.specular = Entry("spec", specular) | |
self.glossy = Entry("glossy", Value(glossy)) | |
def export(self, indent, f): | |
f.write(f"{' ' * indent}shader {{\n") | |
for x in [self.name, self.type, self.diffuse, self.specular, self.glossy]: | |
x.export(indent + lvl, f) | |
f.write(f"{' ' * indent}}}\n") | |
class Image: | |
def __init__(self, width: int, height: int): | |
self.resolution = Entry("resolution", Values([Value(width), Value(height)])) | |
def export(self, indent, f): | |
f.write(f"{' ' * indent}image {{\n") | |
self.resolution.export(indent + lvl, f) | |
f.write(f"{' ' * indent}}}\n") | |
def triangulate(mesh): | |
import bmesh | |
b = bmesh.new() | |
b.from_mesh(mesh) | |
bmesh.ops.triangulate(b, faces=b.faces) | |
b.to_mesh(mesh) | |
b.free() | |
def export_mesh(depsgraph, object): | |
evaluated = object.evaluated_get(depsgraph) | |
mesh = evaluated.to_mesh() | |
triangulate(mesh) | |
mesh.transform(object.matrix_world) | |
uv_layer = mesh.uv_layers.active.data[:] if mesh.uv_layers.active is not None else None | |
s = Shaders() | |
p = Points() | |
t = Triangles() | |
n = Normals() | |
u = UVs() if uv_layer is not None else UVsNone() | |
fs = FaceShaders() | |
m = GenericMesh(object.name, s, p, t, n, u, fs) | |
for slot in object.material_slots: | |
s.add(slot.name) | |
for v in mesh.vertices: | |
p.add(Vector(*v.co)) | |
for f in mesh.polygons: | |
t.add(Triangle(*f.vertices)) | |
n.add(*(Vector(*mesh.loops[i].normal) for i in f.loop_indices)) | |
if uv_layer: | |
u.add(*(Point(*uv_layer[i].uv) for i in f.loop_indices)) | |
fs.add(f.material_index) | |
return m | |
def export_material(material): | |
wrapped = node_shader_utils.PrincipledBSDFWrapper(material) | |
base = wrapped.base_color | |
diffuse = Color(base[0], base[1], base[2]) | |
tint = wrapped.specular_tint | |
s = wrapped.specular | |
specular = Color(s * tint[0], s * tint[1], s * tint[2]) | |
glossy = 1.0 - wrapped.roughness | |
return Shader(material.name, diffuse, specular, glossy) | |
def export_camera(scene): | |
camera = scene.camera | |
tx = camera.matrix_world | |
eye = tx @ mathutils.Vector((0, 0, 0, 1)) | |
target = tx @ mathutils.Vector((0, 0, -1, 1)) | |
up = tx @ mathutils.Vector((0, 1, 0, 0)) | |
fov = math.degrees(camera.data.angle_x) | |
render = scene.render | |
width = render.resolution_x | |
height = render.resolution_y | |
aspect = width / height | |
return Camera(Vector(eye[0], eye[1], eye[2]), Vector(target[0], target[1], target[2]), Vector(up[0], up[1], up[2]), fov, aspect) | |
def export_light(light): | |
color = Color(*light.data.color) | |
tx = light.matrix_world | |
type = light.data.type | |
print(type) | |
if type == "POINT": | |
power = light.data.energy | |
position = tx @ mathutils.Vector((0, 0, 0, 1)) | |
return PointLight(color, power, Vector(position[0], position[1], position[2])) | |
elif type == "SPOT": | |
source = tx @ mathutils.Vector((0, 0, 0, 1)) | |
target = tx @ mathutils.Vector([0, 0, -1, 1]) | |
angle = math.radians(light.data.spot_size) | |
radius = light.data.cutoff_distance / math.cos(angle) * math.sin(angle) | |
return SpotLight(color, Vector(source[0], source[1], source[2]), Vector(target[0], target[1], target[2]), radius) | |
elif type == "SUN": | |
direction = tx.inverted().row[2] | |
return SunLight(Vector(direction[0], direction[1], direction[2])) | |
def export_render(scene): | |
render = scene.render | |
width = render.resolution_x | |
height = render.resolution_y | |
return Image(width, height) | |
def save(output): | |
selection = bpy.context.selected_objects | |
scene = bpy.context.scene | |
depsgraph = bpy.context.evaluated_depsgraph_get() | |
exports = [] | |
exports.append(export_render(scene)) | |
exports.append(export_camera(scene)) | |
materials = {} | |
objects = [] | |
for object in selection: | |
if object.type == "MESH": | |
for slot in object.material_slots: | |
materials[slot.name] = slot.material | |
objects.append(object) | |
elif object.type == "LIGHT": | |
exports.append(export_light(object)) | |
for material in materials.values(): | |
exports.append(export_material(material)) | |
for object in objects: | |
exports.append(export_mesh(depsgraph, object)) | |
Objects(exports).export(0, output) | |
if __name__ == "__main__": | |
#output = io.StringIO() | |
#save(output) | |
#print(output.getvalue()) | |
with open("/home/ziga/Development/projects/SunflowScenes/src/main/java/scene/collection/scenes/files/test4.sc", "w") as f: | |
save(f) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment