code: gcc ptf.c -lm -Ofast -march=native -o ptf

repo_name: AstroImpact/AstroImpact

license: mit


code:
# SPDX-License-Identifier: GPL-2.0-or-later

"""
This script exports Stanford PLY files from Blender. It supports normals,
colors, and texture coordinates per face or per vertex.
"""

import bpy

def _write_binary(fw, ply_verts: list, ply_faces: list) -> None:
    from struct import pack

    # Vertex data
    # ---------------------------

    for v, normal, uv, color in ply_verts:
        fw(pack("<3f", *v.co))
        if normal is not None:
            fw(pack("<3f", *normal))
        if uv is not None:
            fw(pack("<2f", *uv))
        if color is not None:
            fw(pack("<4B", *color))

    # Face data
    # ---------------------------

    for pf in ply_faces:
        length = len(pf)
        fw(pack(f"<B{length}I", length, *pf))

def _write_ascii(fw, ply_verts: list, ply_faces: list) -> None:

    # Vertex data
    # ---------------------------

    for v, normal, uv, color in ply_verts:
        fw(b"%.6f %.6f %.6f" % v.co[:])
        if normal is not None:
            fw(b" %.6f %.6f %.6f" % normal[:])
        if uv is not None:
            fw(b" %.6f %.6f" % uv)
        if color is not None:
            fw(b" %u %u %u %u" % color)
        fw(b"\n")

    # Face data
    # ---------------------------

    for pf in ply_faces:
        fw(b"%d" % len(pf))
        for index in pf:
            fw(b" %d" % index)
        fw(b"\n")

def save_mesh(filepath, bm, use_ascii, use_normals, use_uv, use_color):
    uv_lay = bm.loops.layers.uv.active
    col_lay = bm.loops.layers.color.active

    use_uv = use_uv and uv_lay is not None
    use_color = use_color and col_lay is not None
    normal = uv = color = None

    ply_faces = []
    ply_verts = []
    ply_vert_map = {}
    ply_vert_id = 0

    for f in bm.faces:
        pf = []
        ply_faces.append(pf)

        for loop in f.loops:
            v = map_id = loop.vert

            if use_uv:
                uv = loop[uv_lay].uv[:]
                map_id = v, uv

            # Identify vertex by pointer unless exporting UVs,
            # in which case id by UV coordinate (will split edges by seams).
            if (_id := ply_vert_map.get(map_id)) is not None:
                pf.append(_id)
                continue

            if use_normals:
                normal = v.normal
            if use_color:
                color = tuple(int(x * 255.0) for x in loop[col_lay])

            ply_verts.append((v, normal, uv, color))
            ply_vert_map[map_id] = ply_vert_id
            pf.append(ply_vert_id)
            ply_vert_id += 1

    with open(filepath, "wb") as file:
        fw = file.write
        file_format = b"ascii" if use_ascii else b"binary_little_endian"

        # Header
        # ---------------------------

        fw(b"ply\n")
        fw(b"format %s 1.0\n" % file_format)
        fw(b"comment Created by Blender %s - www.blender.org\n" % bpy.app.version_string.encode("utf-8"))

        fw(b"element vertex %d\n" % len(ply_verts))
        fw(
            b"property float x\n"
            b"property float y\n"
            b"property float z\n"
        )
        if use_normals:
            fw(
                b"property float nx\n"
                b"property float ny\n"
                b"property float nz\n"
            )
        if use_uv:
            fw(
                b"property float s\n"
                b"property float t\n"
            )
        if use_color:
            fw(
                b"property uchar red\n"
                b"property uchar green\n"
                b"property uchar blue\n"
                b"property uchar alpha\n"
            )

        fw(b"element face %d\n" % len(ply_faces))
        fw(b"property list uchar uint vertex_indices\n")
        fw(b"end_header\n")

        # Geometry
        # ---------------------------

        if use_ascii:
            _write_ascii(fw, ply_verts, ply_faces)
        else:
            _write_binary(fw, ply_verts, ply_faces)

def save(
    context,
    filepath="",
    use_ascii=False,
    use_selection=False,
    use_mesh_modifiers=True,
    use_normals=True,
    use_uv_coords=True,
    use_colors=True,
    global_matrix=None,
):
    import time
    import bmesh

    t = time.time()

    if bpy.ops.object.mode_set.poll():
        bpy.ops.object.mode_set(mode='OBJECT')

    if use_selection:
        obs = context.selected_objects
    else:
        obs = context.scene.objects

    depsgraph = context.evaluated_depsgraph_get()
    bm = bmesh.new()

    for ob in obs:
        if use_mesh_modifiers:
            ob_eval = ob.evaluated_get(depsgraph)
        else:
            ob_eval = ob

        try:
            me = ob_eval.to_mesh()
        except RuntimeError:
            continue

        me.transform(ob.matrix_world)
        bm.from_mesh(me)
        ob_eval.to_mesh_clear()

    # Workaround for hardcoded unsigned char limit in other DCCs PLY importers
    if (ngons := [f for f in bm.faces if len(f.verts) > 255]):
        bmesh.ops.triangulate(bm, faces=ngons)

    if global_matrix is not None:
        bm.transform(global_matrix)

    if use_normals:
        bm.normal_update()

    save_mesh(
        filepath,
        bm,
        use_ascii,
        use_normals,
        use_uv_coords,
        use_colors,
    )

    bm.free()

    t_delta = time.time() - t
    print(f"Export completed {filepath!r} in {t_delta:.3f}")


code:
/*
    James William Fletcher (github.com/mrbid)
        October 2022

    !! auto detects if index buffer is GLubyte, GLushort or GLuint.
    !! this is not particularly fast.

    Converts ASCII PLY (.ply) file to C OpenGL buffers.

    Designed for small model files with no more than 65,535 vertices
    as GLushort is used for the index buffer and there is a hard coded
    limit on the buffers used to generate the file output (MAX_BUFF).

    This is basically perfect for converting low poly ASCII PLY
    files to C OpenGL buffers for use in OpenGL ES applications.

    I'm not a high poly modeller and I like to target low end hardware.

    Compile: gcc ptf.c -lm -Ofast -o ptf
    Usage: ./ptf filename_noextension

    **********************************************************************
    An older version of this that uses GLuint for the index buffer
    and no fixed output buffers can be found here:
    https://github.com/esAux/esAux-Menger/blob/main/PTO/pto.c
    but this solution needs to be updated to use the %g format specifier
    and in some instances has the potential to garble file output.
    **********************************************************************
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>

#define MAX_BUFF 1161326592 //387108864 //24194304 //6048576 //1048576

int main(int argc, char** argv)
{
    // start time
    time_t st = time(0);

    // ensure an input file is specified
    if(argc < 2)
    {
        printf("Please specify an input file.\n");
        return 0;
    }

    // take the input file name and strip any supplied extension
    char name[32] = {0};
    strcat(name, argv[1]);
    char* p = strstr(name, ".");
    if(p != NULL){*p = 0x00;}

    // generate the read file path (reads .ply files from a local `ply/` directory)
    char readfile[32] = {0};
    strcat(readfile, "ply/");
    strcat(readfile, name);
    strcat(readfile, ".ply");

    // pre-init our buffers
    char* vertex_array = calloc(1, MAX_BUFF);
    char* index_array = calloc(1, MAX_BUFF);
    char* normal_array = calloc(1, MAX_BUFF);
    char* color_array = calloc(1, MAX_BUFF);
    if(vertex_array == NULL || index_array == NULL || normal_array == NULL || color_array == NULL)
    {
        printf("Failed to allocate memory.\n");
        return 0;
    }

    // open our ASCII PLY file for reading
    unsigned int numvert=0, numind=0, maxind=0;
    unsigned int mode = 0, mode2 = 0;
    printf("Open: %s\n", readfile);
    FILE* f = fopen(readfile, "r");
    while(f == NULL)
    {
        f = fopen(readfile, "r");
        sleep(1);
    }

    // do the conversion
    char line[256];
    char add[256];
    while(fgets(line, 256, f) != NULL)
    {
        //printf("%s\n",line);
        if(strcmp(line, "end_header\n") == 0)
        {
            mode = 1;
            continue;
        }

        // load index
        if(mode == 2)
        {
            unsigned int n,x,y,z;
            if(sscanf(line, "%u %u %u %u", &n, &x, &y, &z) == 4)
            {
                char add[256];
                sprintf(add, "%u,%u,%u,", x, y, z);
                strcat(index_array, add);
                if(x > maxind){maxind = x;}
                if(y > maxind){maxind = y;}
                if(z > maxind){maxind = z;}
                numind += 3;
            }
        }

        // load vertex, normal, color
        if(mode == 1)
        {
            float vx,vy,vz,nx,ny,nz,r,g,b;
            if((mode2 == 0 || mode2 == 1) && sscanf(line, "%f %f %f %f %f %f %f %f %f", &vx, &vy, &vz, &nx, &ny, &nz, &r, &g, &b) == 9)
            {
                sprintf(add, "%g,%g,%g,", vx, vy, vz);
                strcat(vertex_array, add);
                numvert++;

                sprintf(add, "%g,%g,%g,", nx, ny, nz);
                strcat(normal_array, add);

                sprintf(add, "%.3g,%.3g,%.3g,", 0.003921568859f*r, 0.003921568859f*g, 0.003921568859f*b);
                strcat(color_array, add);

                mode2 = 1;
            }
            else if((mode2 == 0 || mode2 == 2) && sscanf(line, "%f %f %f %f %f %f %f %f", &vx, &vy, &vz, &nx, &ny, &nz, &r, &g) == 8)
            {

                sprintf(add, "%g,%g,%g,", vx, vy, vz);
                strcat(vertex_array, add);
                numvert++;

                sprintf(add, "%g,%g,%g,", nx, ny, nz);
                strcat(normal_array, add);

                sprintf(add, "%g,%g,", r, g);
                strcat(color_array, add);

                mode2 = 2;
            }
            else if((mode2 == 0 || mode2 == 3) && sscanf(line, "%f %f %f %f %f %f %f", &vx, &vy, &vz, &r, &g, &b, &nx) == 7)
            {
                sprintf(add, "%g,%g,%g,", vx, vy, vz);
                strcat(vertex_array, add);
                numvert++;

                sprintf(add, "%.3g,%.3g,%.3g,", 0.003921568859f*r, 0.003921568859f*g, 0.003921568859f*b);
                strcat(color_array, add);

                mode2 = 3;
            }
            else if((mode2 == 0 || mode2 == 4) && sscanf(line, "%f %f %f %f %f %f", &vx, &vy, &vz, &nx, &ny, &nz) == 6)
            {
                sprintf(add, "%g,%g,%g,", vx, vy, vz);
                strcat(vertex_array, add);
                numvert++;

                sprintf(add, "%g,%g,%g,", nx, ny, nz);
                strcat(normal_array, add);

                mode2 = 4;
            }
            else if((mode2 == 0 || mode2 == 5) && sscanf(line, "%f %f %f", &vx, &vy, &vz) == 3)
            {
                if(vx == 3.0 && vy == 0.0 && vz == 1.0)
                {
                    strcat(index_array, "0,1,2,");
                    numind += 3;
                    mode = 2;
                    continue;
                }

                sprintf(add, "%g,%g,%g,", vx, vy, vz);
                strcat(vertex_array, add);
                numvert++;

                mode2 = 5;
            }
            else
            {
                strcat(index_array, "0,1,2,");
                numind += 3;
                mode = 2;
                continue;
            }
        }

    }

    // close PLY file
    fclose(f);

    // remove trailing comma's
    if(vertex_array[0] != 0x00)
        vertex_array[strlen(vertex_array)-1] = 0x00;
    if(normal_array[0] != 0x00)
        normal_array[strlen(normal_array)-1] = 0x00;
    if(index_array[0] != 0x00)
        index_array[strlen(index_array)-1] = 0x00;
    if(color_array[0] != 0x00)
        color_array[strlen(color_array)-1] = 0x00;

    // output the resultant file
    char outfile[256];
    sprintf(outfile, "%s.h", name);
    f = fopen(outfile, "w");
    while(f == NULL)
    {
        f = fopen(outfile, "w");
        sleep(1);
    }

    fprintf(f, "\n#ifndef %s_H\n#define %s_H\n\nconst GLfloat %s_vertices[] = {%s};\n", name, name, name, vertex_array);
    if(normal_array[0] != 0x00)
        fprintf(f, "const GLfloat %s_normals[] = {%s};\n", name, normal_array);
    if(color_array[0] != 0x00)
    {
        if(mode2 == 2)
            fprintf(f, "const GLfloat %s_uvmap[] = {%s};\n", name, color_array);
        else
            fprintf(f, "const GLfloat %s_colors[] = {%s};\n", name, color_array);
    }

    if(maxind <= 255)
    {
        fprintf(f, "const GLubyte %s_indices[] = {%s};\nconst GLsizeiptr %s_numind = %u;\nconst GLsizeiptr %s_numvert = %u;\n\n#endif\n", name, index_array, name, numind, name, numvert);
        printf("Output: %s.h (UBYTE)\nMODE: %u\n", name, mode2);
    }
    else if(maxind <= 65535)
    {
        fprintf(f, "const GLushort %s_indices[] = {%s};\nconst GLsizeiptr %s_numind = %u;\nconst GLsizeiptr %s_numvert = %u;\n\n#endif\n", name, index_array, name, numind, name, numvert);
        printf("Output: %s.h (USHORT)\nMODE: %u\n", name, mode2);
    }
    else
    {
        fprintf(f, "const GLuint %s_indices[] = {%s};\nconst GLsizeiptr %s_numind = %u;\nconst GLsizeiptr %s_numvert = %u;\n\n#endif\n", name, index_array, name, numind, name, numvert);
        printf("Output: %s.h (UINT)\nMODE: %u\n", name, mode2);
    }

    const float mins = ((float)(time(0)-st))/60.f;
    if(mins > 0.001f){printf("Time Taken: %.2f mins\n", mins);}
    fclose(f);
    return 0;
}