# Apache License, Version 2.0
'''
Created compact byte arrays which can be decoded into 2D shapes.
(See 'GPU_batch_from_poly_2d_encoded').

- Objects must use the prefix "shape_"
- Meshes and Curves are supported as input.
- C and Python output is written to "output/"

The format is simple: a series of  (X, Y) locations one byte each.
Repeating the same value terminates the polygon, moving onto the next.

Example Use::

   blender.bin -b --factory-startup my_shapes.blend --python make_shape_2d_from_blend.py
'''
import bpy
import os

USE_C_STYLE = True
USE_PY_STYLE = True

WRAP_LIMIT = 79
TAB_WIDTH = 4

SUBDIR = "output"


def float_to_ubyte(f):
    return max(0, min(255, int(round(f * 255.0))))


def curve_to_loops(ob):
    import bmesh
    cu = ob.data

    # Support 2.7x & 2.8x
    if bpy.app.version >= (2, 8):
        me = ob.to_mesh(bpy.context.scene, bpy.context.view_layer, True, 'PREVIEW', calc_tessface=False)
    else:
        me = ob.to_mesh(bpy.context.scene, True, 'PREVIEW', calc_tessface=False)

    bm = bmesh.new()
    bm.from_mesh(me)
    bmesh.ops.beautify_fill(bm, faces=bm.faces, edges=bm.edges)

    edges = bm.edges[:]
    edges.sort(key=lambda e: e.calc_length(), reverse=True)

    for e in edges:
        if e.is_manifold:
            f_a, f_b = [f for f in e.link_faces]
            bmesh.utils.face_join((f_a, f_b), False)

    edges = bm.edges[:]
    for e in edges:
        if e.is_wire:
            bm.edges.remove(e)

    bm.normal_update()

    data_all = []
    for f in bm.faces:
        points = []
        # Ensure all faces are pointing the correct direction
        # Note, we may want to use polygon sign for a second color
        # (via the material index).
        if f.normal.z > 0.0:
            loops = f.loops
        else:
            loops = reversed(f.loops)
        for l in loops:
            points.append(
                tuple(float_to_ubyte(axis) for axis in l.vert.co.xy)
            )
        data_all.append((points, f.material_index))
    bm.free()
    bpy.data.meshes.remove(me)
    return data_all


def write_c(ob):
    cu = ob.data
    name = ob.name
    with open(os.path.join(SUBDIR, name + ".c"), 'w') as fh:
        fw = fh.write
        fw(f"/* {name} */\n")
        fw(f"const uchar {name}[] = {{")
        line_len = WRAP_LIMIT
        line_is_first = True
        array_len = 0
        data_all = curve_to_loops(ob)
        for (points, material_index) in data_all:
            # TODO, material_index
            for p in points + [points[-1]]:
                line_len += 12
                if line_len >= WRAP_LIMIT:
                    fw("\n\t")
                    line_len = TAB_WIDTH
                    line_is_first = True
                if not line_is_first:
                    fw(" ")
                fw(", ".join([f"0x{axis:02x}" for axis in p]) + ",")
                line_is_first = False
            array_len += (len(points) + 1) * 2
        fw("\n};\n")
        # fw(f"const int data_len = {array_len}\n")


def write_py(ob):
    cu = ob.data
    name = ob.name
    with open(os.path.join(SUBDIR, name + ".py"), 'w') as fh:
        fw = fh.write
        fw(f"# {name}\n")
        fw("data = (")
        line_len = WRAP_LIMIT
        fw = fh.write
        data_all = curve_to_loops(ob)
        for (points, material_index) in data_all:
            # TODO, material_index
            for p in points + [points[-1]]:
                line_len += 8
                if line_len >= WRAP_LIMIT:
                    if p is not points[0]:
                        fw("'")
                    fw("\n    b'")
                    line_len = 6
                fw("".join([f"\\x{axis:02x}" for axis in p]))
        fw("'\n)\n")


def main():
    os.makedirs(SUBDIR, exist_ok=True)
    for ob in bpy.data.objects:
        if ob.type not in {'MESH', 'CURVE'}:
            continue
        if not ob.name.startswith('shape_'):
            continue
        if USE_C_STYLE:
            write_c(ob)
        if USE_PY_STYLE:
            write_py(ob)


if __name__ == "__main__":
    main()
