"""Portable compare props: rock, tree and arena tile for the Godot/Unreal stage. Blender Z up, -Y forward. Same style family as the Verdant Rift sanctuary. """ from pathlib import Path import bpy, bmesh, math, random, json, hashlib from mathutils import Vector from math import sin, cos, pi P = Path(__file__).resolve().parents[2] OUT = P / 'assets/compare' OUT.mkdir(parents=True, exist_ok=True) SRC = Path(__file__).resolve().parent random.seed(77) bpy.ops.wm.read_factory_settings(use_empty=True) def material(name, color, rough=.85): m = bpy.data.materials.new('CV_' + name) m.diffuse_color = (*color, 1) m.use_nodes = True b = m.node_tree.nodes.get('Principled BSDF') b.inputs['Base Color'].default_value = (*color, 1) b.inputs['Roughness'].default_value = rough n = m.node_tree.nodes l = m.node_tree.links v = n.new('ShaderNodeVertexColor') v.layer_name = 'SurfaceColor' l.new(v.outputs['Color'], b.inputs['Base Color']) return m M = { 'cliff': material('Cliff', (.42, .40, .38), .92), 'limestone': material('Limestone', (.62, .58, .50), .88), 'bark': material('Bark', (.22, .14, .08), .9), 'leaf': material('Leaf', (.28, .42, .12), .78), 'leaf2': material('LeafLight', (.40, .55, .18), .75), 'paving': material('Paving', (.38, .36, .34), .82), } def mesh(name, verts, faces, kind, uv=None, colors=None, smooth=True): me = bpy.data.meshes.new(name) me.from_pydata(verts, [], faces) me.update() ob = bpy.data.objects.new(name, me) bpy.context.collection.objects.link(ob) me.materials.append(M[kind]) for f in me.polygons: f.use_smooth = smooth tex = me.uv_layers.new(name='UVMap') col = me.color_attributes.new(name='SurfaceColor', type='FLOAT_COLOR', domain='CORNER') base = M[kind].diffuse_color[:3] for f in me.polygons: for li in f.loop_indices: i = me.loops[li].vertex_index v = verts[i] tex.data[li].uv = uv[i] if uv else (v[0] * .5, (v[1] + v[2]) * .5) fac = .90 + .10 * sin(v[0] * 13 + v[2] * 7) + .04 * cos(v[1] * 29) c = colors[i] if colors else tuple(max(.01, x * fac) for x in base) col.data[li].color = (*c, 1) return ob def finish(ob): bm = bmesh.new() bm.from_mesh(ob.data) bmesh.ops.remove_doubles(bm, verts=list(bm.verts), dist=.0001) bmesh.ops.triangulate(bm, faces=list(bm.faces)) bmesh.ops.recalc_face_normals(bm, faces=list(bm.faces)) if bm.calc_volume(signed=True) < 0: bmesh.ops.reverse_faces(bm, faces=list(bm.faces)) bm.to_mesh(ob.data) bm.free() ob.data.update() def export(ob, name, record_id, role): bpy.ops.object.select_all(action='DESELECT') ob.select_set(True) bpy.context.view_layer.objects.active = ob path = OUT / (name + '.glb') bpy.ops.export_scene.gltf( filepath=str(path), export_format='GLB', use_selection=True, export_animations=False, export_materials='EXPORT', export_yup=True) raw = path.read_bytes() import struct doc = json.loads(raw[20:20 + struct.unpack_from('