Skip to content

Instantly share code, notes, and snippets.

@brokenpylons
Last active October 9, 2024 18:18
Show Gist options
  • Save brokenpylons/dcc4005fa1264bd7da82cf74d079704a to your computer and use it in GitHub Desktop.
Save brokenpylons/dcc4005fa1264bd7da82cf74d079704a to your computer and use it in GitHub Desktop.
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