forked from bartvdbraak/blender
331 lines
8.6 KiB
Python
331 lines
8.6 KiB
Python
# Apache License, Version 2.0
|
|
|
|
"""
|
|
Example Usage
|
|
=============
|
|
|
|
Command line::
|
|
|
|
./blender.bin \
|
|
icon_file.blend --background --python ./release/datafiles/blender_icons_geom.py -- \
|
|
--output-dir=./release/datafiles/blender_icons_geom
|
|
|
|
Icon Format
|
|
===========
|
|
|
|
This is a simple binary format (all bytes, so no endian).
|
|
|
|
The header is 8 bytes:
|
|
|
|
:0..3: ``VCO``: identifier.
|
|
:4: ``0``: icon file version.
|
|
:5: icon size-x.
|
|
:6: icon size-y.
|
|
:7: icon start-x.
|
|
:8: icon start-y.
|
|
|
|
Icon width and height are for icons that don't use the full byte range
|
|
(so we don't get bad alignment for 48 pixel grid for eg).
|
|
|
|
Start values are currently unused.
|
|
|
|
After the header, the remaining length of the data defines the geometry size.
|
|
|
|
:6 bytes each: triangle (XY) locations.
|
|
:12 bytes each: triangle (RGBA) locations.
|
|
|
|
All coordinates are written, then all colors.
|
|
|
|
Since this is a binary format which isn't intended for general use
|
|
the ``.dat`` file extension should be used.
|
|
"""
|
|
|
|
# This script writes out geometry-icons.
|
|
import bpy
|
|
|
|
|
|
class TriMesh:
|
|
"""
|
|
Triangulate, may apply other changes here too.
|
|
"""
|
|
__slots__ = ("object", "mesh")
|
|
|
|
def __init__(self, ob):
|
|
self.object = ob
|
|
self.mesh = None
|
|
|
|
def __enter__(self):
|
|
self.mesh = self._tri_copy_from_object(self.object)
|
|
return self.mesh
|
|
|
|
def __exit__(self, *args):
|
|
bpy.data.meshes.remove(self.mesh)
|
|
|
|
@staticmethod
|
|
def _tri_copy_from_object(ob):
|
|
import bmesh
|
|
assert(ob.type == 'MESH')
|
|
bm = bmesh.new()
|
|
bm.from_mesh(ob.data)
|
|
bmesh.ops.triangulate(bm, faces=bm.faces)
|
|
me = bpy.data.meshes.new(ob.name + ".copy")
|
|
bm.to_mesh(me)
|
|
bm.free()
|
|
return me
|
|
|
|
|
|
def object_material_colors(ob):
|
|
material_colors = []
|
|
color_default = (1.0, 1.0, 1.0, 1.0)
|
|
for slot in ob.material_slots:
|
|
material = slot.material
|
|
color = color_default
|
|
if material is not None and material.use_nodes:
|
|
node_tree = material.node_tree
|
|
if node_tree is not None:
|
|
color = next((
|
|
node.outputs[0].default_value[:]
|
|
for node in node_tree.nodes
|
|
if node.type == 'RGB'
|
|
), color_default)
|
|
material_colors.append(color)
|
|
return material_colors
|
|
|
|
|
|
def object_child_map(objects):
|
|
objects_children = {}
|
|
for ob in objects:
|
|
ob_parent = ob.parent
|
|
# Get the root.
|
|
if ob_parent is not None:
|
|
while ob_parent and ob_parent.parent:
|
|
ob_parent = ob_parent.parent
|
|
if ob_parent is not None:
|
|
objects_children.setdefault(ob_parent, []).append(ob)
|
|
for ob_all in objects_children.values():
|
|
ob_all.sort(key=lambda ob: ob.name)
|
|
return objects_children
|
|
|
|
|
|
def mesh_data_lists_from_mesh(me, material_colors):
|
|
me_loops = me.loops[:]
|
|
me_loops_color = me.vertex_colors.active.data[:]
|
|
me_verts = me.vertices[:]
|
|
me_polys = me.polygons[:]
|
|
|
|
tris_data = []
|
|
|
|
for p in me_polys:
|
|
# Backface culling (allows using spheres without tedious manual deleting).
|
|
if p.normal.z <= 0.0:
|
|
continue
|
|
|
|
material_index = p.material_index
|
|
if material_index < len(material_colors):
|
|
base_color = material_colors[p.material_index]
|
|
else:
|
|
base_color = (1.0, 1.0, 1.0, 1.0)
|
|
|
|
l_sta = p.loop_start
|
|
l_len = p.loop_total
|
|
loops_poly = me_loops[l_sta:l_sta + l_len]
|
|
color_poly = me_loops_color[l_sta:l_sta + l_len]
|
|
i0 = 0
|
|
i1 = 1
|
|
|
|
# we only write tris now
|
|
assert(len(loops_poly) == 3)
|
|
|
|
for i2 in range(2, l_len):
|
|
l0 = loops_poly[i0]
|
|
l1 = loops_poly[i1]
|
|
l2 = loops_poly[i2]
|
|
|
|
c0 = color_poly[i0]
|
|
c1 = color_poly[i1]
|
|
c2 = color_poly[i2]
|
|
|
|
v0 = me_verts[l0.vertex_index]
|
|
v1 = me_verts[l1.vertex_index]
|
|
v2 = me_verts[l2.vertex_index]
|
|
|
|
tris_data.append((
|
|
# float depth
|
|
p.center.z,
|
|
# XY coords.
|
|
(
|
|
v0.co.xy[:],
|
|
v1.co.xy[:],
|
|
v2.co.xy[:],
|
|
),
|
|
# RGBA color.
|
|
tuple((
|
|
[int(c * b * 255) for c, b in zip(cn.color, base_color)]
|
|
for cn in (c0, c1, c2)
|
|
)),
|
|
))
|
|
i1 = i2
|
|
return tris_data
|
|
|
|
|
|
def mesh_data_lists_from_objects(ob_parent, ob_children):
|
|
tris_data = []
|
|
|
|
has_parent = False
|
|
if ob_children:
|
|
parent_matrix = ob_parent.matrix_world.copy()
|
|
parent_matrix_inverted = parent_matrix.inverted()
|
|
|
|
for ob in (ob_parent, *ob_children):
|
|
with TriMesh(ob) as me:
|
|
if has_parent:
|
|
me.transform(parent_matrix_inverted * ob.matrix_world)
|
|
|
|
tris_data.extend(
|
|
mesh_data_lists_from_mesh(
|
|
me,
|
|
object_material_colors(ob),
|
|
)
|
|
)
|
|
has_parent = True
|
|
return tris_data
|
|
|
|
|
|
def write_mesh_to_py(fh, ob, ob_children):
|
|
|
|
def float_as_byte(f, axis_range):
|
|
assert(axis_range <= 255)
|
|
# -1..1 -> 0..255
|
|
f = (f + 1.0) * 0.5
|
|
f = int(round(f * axis_range))
|
|
return min(max(f, 0), axis_range)
|
|
|
|
def vert_as_byte_pair(v):
|
|
return (
|
|
float_as_byte(v[0], coords_range_align[0]),
|
|
float_as_byte(v[1], coords_range_align[1]),
|
|
)
|
|
|
|
tris_data = mesh_data_lists_from_objects(ob, ob_children)
|
|
|
|
# 100 levels of Z depth, round to avoid differences from precision error
|
|
# causing different computers to write triangles in more or less random order.
|
|
tris_data.sort(key=lambda data: int(data[0] * 100))
|
|
|
|
if 0:
|
|
# make as large as we can, keeping alignment
|
|
def size_scale_up(size):
|
|
assert(size != 0)
|
|
while size * 2 <= 255:
|
|
size *= 2
|
|
return size
|
|
|
|
coords_range = (
|
|
size_scale_up(ob.get("size_x")) or 255,
|
|
size_scale_up(ob.get("size_y")) or 255,
|
|
)
|
|
else:
|
|
# disable for now
|
|
coords_range = 255, 255
|
|
|
|
# Pixel size needs to be increased since a pixel needs one extra geom coordinate,
|
|
# if we're writing 32 pixel, align verts to 33.
|
|
coords_range_align = tuple(min(c + 1, 255) for c in coords_range)
|
|
|
|
print("Writing:", fh.name, coords_range)
|
|
|
|
fw = fh.write
|
|
|
|
# Header (version 0).
|
|
fw(b'VCO\x00')
|
|
# Width, Height
|
|
fw(bytes(coords_range))
|
|
# X, Y
|
|
fw(bytes((0, 0)))
|
|
|
|
for (_, tri_coords, _) in tris_data:
|
|
for vert in tri_coords:
|
|
fw(bytes(vert_as_byte_pair(vert)))
|
|
for (_, _, tri_color) in tris_data:
|
|
for color in tri_color:
|
|
fw(bytes(color))
|
|
|
|
|
|
def create_argparse():
|
|
import argparse
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument(
|
|
"--output-dir",
|
|
dest="output_dir",
|
|
default=".",
|
|
type=str,
|
|
metavar="DIR",
|
|
required=False,
|
|
help="Directory to write icons to.",
|
|
)
|
|
parser.add_argument(
|
|
"--group",
|
|
dest="group",
|
|
default="",
|
|
type=str,
|
|
metavar="GROUP",
|
|
required=False,
|
|
help="Group name to export from (otherwise export all objects).",
|
|
)
|
|
return parser
|
|
|
|
|
|
def main():
|
|
import os
|
|
import sys
|
|
parser = create_argparse()
|
|
if "--" in sys.argv:
|
|
argv = sys.argv[sys.argv.index("--") + 1:]
|
|
else:
|
|
argv = []
|
|
args = parser.parse_args(argv)
|
|
|
|
objects = []
|
|
|
|
if args.group:
|
|
group = bpy.data.collections.get(args.group)
|
|
if group is None:
|
|
print(f"Group {args.group!r} not found!")
|
|
return
|
|
objects_source = group.objects
|
|
del group
|
|
else:
|
|
objects_source = bpy.data.objects
|
|
|
|
for ob in objects_source:
|
|
|
|
# Skip non-mesh objects
|
|
if ob.type != 'MESH':
|
|
continue
|
|
name = ob.name
|
|
|
|
# Skip copies of objects
|
|
if name.rpartition(".")[2].isdigit():
|
|
continue
|
|
|
|
if not ob.data.vertex_colors:
|
|
print("Skipping:", name, "(no vertex colors)")
|
|
continue
|
|
|
|
objects.append((name, ob))
|
|
|
|
objects.sort(key=lambda a: a[0])
|
|
|
|
objects_children = object_child_map(bpy.data.objects)
|
|
|
|
for name, ob in objects:
|
|
if ob.parent:
|
|
continue
|
|
filename = os.path.join(args.output_dir, name + ".dat")
|
|
with open(filename, 'wb') as fh:
|
|
write_mesh_to_py(fh, ob, objects_children.get(ob, []))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|