Assign name for accessor.

Improve .obj export.
Use clipp to parse cmd options.
Add some handy python scripts.
This commit is contained in:
Syoyo Fujita 2020-03-12 21:31:12 +09:00
parent cf30d42cbc
commit 0ee2120965
10 changed files with 7806 additions and 360 deletions

View File

@ -71,6 +71,7 @@ In extension(`ExtensionMap`), JSON number value is parsed as int or float(number
* [glview](examples/glview) : Simple glTF geometry viewer. * [glview](examples/glview) : Simple glTF geometry viewer.
* [validator](examples/validator) : Simple glTF validator with JSON schema. * [validator](examples/validator) : Simple glTF validator with JSON schema.
* [basic](examples/basic) : Basic glTF viewer with texturing support. * [basic](examples/basic) : Basic glTF viewer with texturing support.
* [mesh-conv](examples/mesh-conv) : Convert glTF mesh to wavefront .obj, wavefront .obj to glTF mesh.
## Projects using TinyGLTF ## Projects using TinyGLTF
@ -216,3 +217,7 @@ We may be better to introduce bounded memory size checking when parsing glTF dat
* catch : Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. Distributed under the Boost Software License, Version 1.0. * catch : Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. Distributed under the Boost Software License, Version 1.0.
* RapidJSON : Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. http://rapidjson.org/ * RapidJSON : Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. http://rapidjson.org/
* dlib(uridecode, uriencode) : Copyright (C) 2003 Davis E. King Boost Software License 1.0. http://dlib.net/dlib/server/server_http.cpp.html * dlib(uridecode, uriencode) : Copyright (C) 2003 Davis E. King Boost Software License 1.0. http://dlib.net/dlib/server/server_http.cpp.html
### Used in examples
* clipp: MIT License. https://github.com/muellan/clipp

View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2017 André Müller; foss@andremueller-online.de
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

7024
examples/common/clipp.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,19 +1,34 @@
# Mesh modify experiment # Mesh modify experiment
Sometimes we want to tweak mesh attributes(e.g. vertex position, uv coord, etc). Sometimes we want to tweak mesh attributes(e.g. vertex position, uv coord, etc).
glTF itself does not allow ASCII representation of such data. glTF itself does not allow ASCII representation of such data so we need to write a converter.
This example show how to This example show how to
- Export mesh data from .bin to .obj - Export mesh data from .bin to .obj
- Import mesh data to .bin(update corresponding buffer data) from .obj - Import mesh data to .bin(update corresponding buffer data) from .obj
## Features
* Support skin weights(`JOINTS_N`, `WEIGHTS_N`)
## Supported attributes
* [x] POSITION
* [x] NORMAL
* [x] TANGENT
* [ ] COLOR (vertex color)
* [x] TEXCOORD_N
* Only single texcoord(uv set) is supported
* Specify `--uvset 1` to specify which UV to use.
* [x] WEIGHTS_N, JOINTS_N
## Usage ## Usage
### Wavefront .obj to glTF ### Wavefront .obj to glTF
``` ```
$ mesh-modify obj2gltf input.obj $ mesh-modify --op=obj2gltf input.obj
``` ```
All shapes in .obj are concatenated and create single glTF mesh. All shapes in .obj are concatenated and create single glTF mesh.
@ -26,7 +41,7 @@ Buffer is stored as external file(`.bin`)
### glTF to Wavefront .obj ### glTF to Wavefront .obj
``` ```
$ mesh-modify gltf2obj input.gltf $ mesh-modify --op=gltf2obj input.gltf
``` ```
.obj will be created for each glTF Mesh. .obj will be created for each glTF Mesh.

View File

@ -2,11 +2,11 @@
#include <cmath> #include <cmath>
#include <cstdio> #include <cstdio>
#include <cstdlib> #include <cstdlib>
#include <fstream>
#include <iostream> #include <iostream>
#include <limits> #include <limits>
#include <string> #include <string>
#include <vector> #include <vector>
#include <fstream>
#if !defined(__ANDROID__) && !defined(_WIN32) #if !defined(__ANDROID__) && !defined(_WIN32)
#include <wordexp.h> #include <wordexp.h>
@ -18,6 +18,7 @@
#endif #endif
#include "../../json.hpp" #include "../../json.hpp"
#include "../common/clipp.h"
using json = nlohmann::json; using json = nlohmann::json;
@ -406,7 +407,9 @@ static uint32_t UnpackIndex(const unsigned char *ptr, int type) {
} }
static bool DumpMesh(const tinygltf::Model &model, const tinygltf::Mesh &mesh, static bool DumpMesh(const tinygltf::Model &model, const tinygltf::Mesh &mesh,
example::MeshPrim *out) { bool verbose, example::MeshPrim *out) {
out->prims.clear();
for (size_t i = 0; i < mesh.primitives.size(); i++) { for (size_t i = 0; i < mesh.primitives.size(); i++) {
const tinygltf::Primitive &primitive = mesh.primitives[i]; const tinygltf::Primitive &primitive = mesh.primitives[i];
@ -415,10 +418,12 @@ static bool DumpMesh(const tinygltf::Model &model, const tinygltf::Mesh &mesh,
return false; return false;
} }
example::PrimSet out_prim;
// indices. // indices.
{ {
const tinygltf::Accessor &indexAccessor = const tinygltf::Accessor &indexAccessor =
model.accessors[size_t(primitive.indices)]; model.accessors[size_t(primitive.indices)];
size_t num_elements = indexAccessor.count; size_t num_elements = indexAccessor.count;
std::cout << "index.elements = " << num_elements << "\n"; std::cout << "index.elements = " << num_elements << "\n";
@ -426,10 +431,11 @@ static bool DumpMesh(const tinygltf::Model &model, const tinygltf::Mesh &mesh,
size_t byte_stride = ComponentTypeByteSize(indexAccessor.componentType); size_t byte_stride = ComponentTypeByteSize(indexAccessor.componentType);
const tinygltf::BufferView &indexBufferView = const tinygltf::BufferView &indexBufferView =
model.bufferViews[size_t(indexAccessor.bufferView)]; model.bufferViews[size_t(indexAccessor.bufferView)];
// should be 34963(ELEMENT_ARRAY_BUFFER) // should be 34963(ELEMENT_ARRAY_BUFFER)
std::cout << "index.target = " << PrintTarget(indexBufferView.target) << "\n"; std::cout << "index.target = " << PrintTarget(indexBufferView.target)
<< "\n";
if (indexBufferView.target != TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER) { if (indexBufferView.target != TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER) {
std::cerr << "indexBufferView.target must be ELEMENT_ARRAY_BUFFER\n"; std::cerr << "indexBufferView.target must be ELEMENT_ARRAY_BUFFER\n";
return false; return false;
@ -441,21 +447,22 @@ static bool DumpMesh(const tinygltf::Model &model, const tinygltf::Mesh &mesh,
std::vector<uint32_t> indices; std::vector<uint32_t> indices;
for (size_t k = 0; k < num_elements; k++) { for (size_t k = 0; k < num_elements; k++) {
// TODO(syoyo): out-of-bounds check. // TODO(syoyo): out-of-bounds check.
const unsigned char *ptr = indexBuffer.data.data() + const unsigned char *ptr = indexBuffer.data.data() +
indexBufferView.byteOffset + (k * byte_stride) + indexBufferView.byteOffset +
indexAccessor.byteOffset; (k * byte_stride) + indexAccessor.byteOffset;
uint32_t idx = UnpackIndex(ptr, indexAccessor.componentType); uint32_t idx = UnpackIndex(ptr, indexAccessor.componentType);
std::cout << "vertex_index[" << k << "] = " << idx << "\n"; if (verbose) {
std::cout << "vertex_index[" << k << "] = " << idx << "\n";
}
indices.push_back(idx); indices.push_back(idx);
} }
out->indices = indices; out_prim.indices = indices;
out->indices_type = indexAccessor.componentType; out_prim.indices_type = indexAccessor.componentType;
} }
// attributes // attributes
@ -522,7 +529,9 @@ static bool DumpMesh(const tinygltf::Model &model, const tinygltf::Mesh &mesh,
bufferView.byteOffset + (k * byte_stride) + bufferView.byteOffset + (k * byte_stride) +
accessor.byteOffset; accessor.byteOffset;
float value = Unpack(ptr, accessor.componentType); float value = Unpack(ptr, accessor.componentType);
std::cout << "[" << k << "] value = " << value << "\n"; if (verbose) {
std::cout << "[" << k << "] value = " << value << "\n";
}
attrib.data.push_back(value); attrib.data.push_back(value);
} }
attrib.component_type = accessor.componentType; attrib.component_type = accessor.componentType;
@ -530,28 +539,27 @@ static bool DumpMesh(const tinygltf::Model &model, const tinygltf::Mesh &mesh,
attrib.name = it->first; attrib.name = it->first;
if (attrib.name.compare("POSITION") == 0) { if (attrib.name.compare("POSITION") == 0) {
out->position = attrib; out_prim.position = attrib;
} else if (attrib.name.compare("NORMAL") == 0) { } else if (attrib.name.compare("NORMAL") == 0) {
out->normal = attrib; out_prim.normal = attrib;
} else if (attrib.name.compare("TANGENT") == 0) { } else if (attrib.name.compare("TANGENT") == 0) {
out->tangent = attrib; out_prim.tangent = attrib;
} else if (attrib.name.rfind("TEXCOORD_", 0) == 0) { } else if (attrib.name.rfind("TEXCOORD_", 0) == 0) {
int id = GetSlotId(attrib.name); int id = GetSlotId(attrib.name);
std::cout << "texcoord[" << id << "]\n"; std::cout << "texcoord[" << id << "]\n";
out->texcoords[id] = attrib; out_prim.texcoords[id] = attrib;
} else if (attrib.name.rfind("JOINTS_", 0) == 0) { } else if (attrib.name.rfind("JOINTS_", 0) == 0) {
int id = GetSlotId(attrib.name); int id = GetSlotId(attrib.name);
std::cout << "joints[" << id << "]\n"; std::cout << "joints[" << id << "]\n";
out->joints[id] = attrib; out_prim.joints[id] = attrib;
} else if (attrib.name.rfind("WEIGHTS_", 0) == 0) { } else if (attrib.name.rfind("WEIGHTS_", 0) == 0) {
int id = GetSlotId(attrib.name); int id = GetSlotId(attrib.name);
std::cout << "weights[" << id << "]\n"; std::cout << "weights[" << id << "]\n";
out->weights[id] = attrib; out_prim.weights[id] = attrib;
} else { } else {
std::cerr << "???: attrib.name = " << attrib.name << "\n"; std::cerr << "???: attrib.name = " << attrib.name << "\n";
return false; return false;
} }
} }
} }
@ -564,15 +572,18 @@ static bool DumpMesh(const tinygltf::Model &model, const tinygltf::Mesh &mesh,
return false; return false;
} }
out->mode = primitive.mode; out_prim.mode = primitive.mode;
out->name = mesh.name;
out->prims.push_back(out_prim);
} }
out->name = mesh.name;
return true; return true;
} }
static bool ExtractMesh(const std::string &asset_path, tinygltf::Model &model, static bool ExtractMesh(const std::string &asset_path, tinygltf::Model &model,
std::vector<example::MeshPrim> *outs) { bool verbose, std::vector<example::MeshPrim> *outs) {
// Get .bin data // Get .bin data
{ {
if (model.buffers.size() != 1) { if (model.buffers.size() != 1) {
@ -595,11 +606,13 @@ static bool ExtractMesh(const std::string &asset_path, tinygltf::Model &model,
search_paths.push_back(asset_path); search_paths.push_back(asset_path);
std::string abs_filepath = FindFile(search_paths, buffer.uri); std::string abs_filepath = FindFile(search_paths, buffer.uri);
std::vector<uint8_t> bin = LoadBin(buffer.uri); std::vector<uint8_t> bin = LoadBin(abs_filepath);
if (bin.size() != buffer.data.size()) { if (bin.size() != buffer.data.size()) {
std::cerr << "Byte size mismatch. Failed to load file: " << buffer.uri std::cerr << "Byte size mismatch. Failed to load file: " << buffer.uri
<< "\n"; << "\n";
std::cerr << " .bin size = " << bin.size() << ", size in 'buffer.uri' = " << buffer.data.size() << "\n"; std::cerr << " Searched absolute file path: " << abs_filepath << "\n";
std::cerr << " .bin size = " << bin.size()
<< ", size in 'buffer.uri' = " << buffer.data.size() << "\n";
return false; return false;
} }
} }
@ -608,7 +621,7 @@ static bool ExtractMesh(const std::string &asset_path, tinygltf::Model &model,
std::cout << "mesh.name: " << mesh.name << "\n"; std::cout << "mesh.name: " << mesh.name << "\n";
example::MeshPrim output; example::MeshPrim output;
bool ret = DumpMesh(model, mesh, &output); bool ret = DumpMesh(model, mesh, verbose, &output);
if (!ret) { if (!ret) {
return false; return false;
} }
@ -622,31 +635,41 @@ static bool ExtractMesh(const std::string &asset_path, tinygltf::Model &model,
} // namespace } // namespace
int main(int argc, char **argv) { int main(int argc, char **argv) {
if (argc < 3) { std::string op;
std::cout << "mesh-modify <op> <args>" << std::endl; std::string input_filename;
std::cout << " op\n\n"; std::string output_filename = "output.gltf";
std::cout << " gltf2obj input.gltf <flip_texcoord_y>\n"; int uvset = 0;
std::cout << " obj2gltf input.obj <verbose>\n"; bool verbose = false;
bool export_skinweight = true;
bool no_flip_texcoord_y = false;
auto cli =
(clipp::required("-i", "--input") &
clipp::value("input filename", input_filename),
clipp::option("-o", "--outout") &
clipp::value("Output filename(obj2fltf)", output_filename),
clipp::option("-v", "--verbose").set(verbose).doc("Verbose output"),
clipp::option("--export_skinweight") &
clipp::value("Export skin weights(gltf2obj). default true.",
export_skinweight),
clipp::option("--uvset").set(uvset).doc("UV set(TEXCOORD_N) to use"),
clipp::option("--op") &
clipp::value("operation mode(`gltf2obj`, `obj2gltf`", op),
clipp::option("--no-flip-texcoord-y")
.set(no_flip_texcoord_y)
.doc("Do not flip texcoord Y"));
if (!clipp::parse(argc, argv, cli)) {
std::cout << clipp::make_man_page(cli, argv[0]);
return EXIT_FAILURE; return EXIT_FAILURE;
} }
std::string op = argv[1];
if (op == "gltf2obj") { if (op == "gltf2obj") {
bool flip_texcoord_y = true;
if (argc > 3) {
flip_texcoord_y = (std::atoi(argv[3]) > 0) ? true : false;
}
tinygltf::Model model; tinygltf::Model model;
tinygltf::TinyGLTF loader; tinygltf::TinyGLTF loader;
std::string err; std::string err;
std::string warn; std::string warn;
std::string input_filename(argv[2]);
std::string ext = GetFilePathExtension(input_filename); std::string ext = GetFilePathExtension(input_filename);
{ {
@ -657,8 +680,8 @@ int main(int argc, char **argv) {
input_filename.c_str()); input_filename.c_str());
} else { } else {
// assume ascii glTF. // assume ascii glTF.
ret = ret = loader.LoadASCIIFromFile(&model, &err, &warn,
loader.LoadASCIIFromFile(&model, &err, &warn, input_filename.c_str()); input_filename.c_str());
} }
if (!warn.empty()) { if (!warn.empty()) {
@ -674,6 +697,7 @@ int main(int argc, char **argv) {
} }
} }
#if 0
json j; json j;
{ {
std::ifstream i(input_filename); std::ifstream i(input_filename);
@ -693,7 +717,7 @@ int main(int argc, char **argv) {
json j_original = R"({ json j_original = R"({
"baz": ["one", "two", "three"], "baz": ["one", "two", "three"],
"foo": "bar" "foo": "bar"
})"_json; })"_json;
//json j_patch = R"([ //json j_patch = R"([
// { "op": "remove", "path": "/buffers" } // { "op": "remove", "path": "/buffers" }
@ -701,44 +725,50 @@ int main(int argc, char **argv) {
std::cout << "patch = " << j_patch.dump(2) << "\n"; std::cout << "patch = " << j_patch.dump(2) << "\n";
json j_ret = j.patch(j_patch); json j_ret = j.patch(j_patch);
std::cout << "patched = " << j_ret.dump(2) << "\n"; std::cout << "patched = " << j_ret.dump(2) << "\n";
#endif
std::string basedir = GetBaseDir(input_filename); std::string basedir = GetBaseDir(input_filename);
std::vector<example::MeshPrim> meshes; std::vector<example::MeshPrim> meshes;
bool ret = ExtractMesh(basedir, model, &meshes); bool ret = ExtractMesh(basedir, model, verbose, &meshes);
size_t n = 0; size_t n = 0;
for (const auto &mesh : meshes) { for (const auto &mesh : meshes) {
// Assume no duplicated name in .glTF data // Assume no duplicated name in .glTF data
std::string filename; std::string basename;
if (mesh.name.empty()) { if (mesh.name.empty()) {
filename = "untitled-" + std::to_string(n) + ".obj"; basename = "untitled-" + std::to_string(n);
} else { } else {
filename = mesh.name + ".obj"; basename = mesh.name;
} }
bool ok = example::SaveAsObjMesh(filename, mesh, flip_texcoord_y);
if (!ok) { for (size_t primid = 0; primid < mesh.prims.size(); primid++) {
return EXIT_FAILURE; example::ObjExportOption options;
options.primid = int(primid);
options.export_skinweights = export_skinweight;
options.uvset = uvset;
options.flip_texcoord_y = !no_flip_texcoord_y;
bool ok = example::SaveAsObjMesh(basename, mesh, options);
if (!ok) {
std::cout << "Failed to export mesh[" << mesh.name << "].primitives["
<< primid << "]\n";
// may ok;
}
} }
n++; n++;
} }
return ret ? EXIT_SUCCESS : EXIT_FAILURE; return ret ? EXIT_SUCCESS : EXIT_FAILURE;
} else if (op == "obj2gltf") { } else if (op == "obj2gltf") {
std::string input_filename(argv[2]);
bool verbose = false;
if (argc > 3) {
verbose = (std::atoi(argv[3]) > 0) ? true : false;
}
// Require facevarying layout? // Require facevarying layout?
// facevarying representation is required if a vertex can have multiple normal/uv value. // facevarying representation is required if a vertex can have multiple
// drawback of facevarying is mesh data increases. // normal/uv value. drawback of facevarying is mesh data increases. false =
// false = try to keep shared vertex representation as much as possible. // try to keep shared vertex representation as much as possible. true =
// true = reorder vertex data and re-assign vertex indices for facevarying data layout. // reorder vertex data and re-assign vertex indices for facevarying data
// layout.
bool facevarying = false; bool facevarying = false;
example::MeshPrim mesh; example::MeshPrim mesh;
@ -751,8 +781,6 @@ int main(int argc, char **argv) {
PrintMeshPrim(mesh); PrintMeshPrim(mesh);
} }
std::string output_filename("output.gltf");
ok = example::SaveAsGLTFMesh(output_filename, mesh); ok = example::SaveAsGLTFMesh(output_filename, mesh);
if (!ok) { if (!ok) {
std::cerr << "Failed to save mesh as glTF\n"; std::cerr << "Failed to save mesh as glTF\n";
@ -761,9 +789,7 @@ int main(int argc, char **argv) {
std::cout << "Write glTF: " << output_filename << "\n"; std::cout << "Write glTF: " << output_filename << "\n";
return EXIT_SUCCESS; return EXIT_SUCCESS;
} else { } else {
std::cerr << "Unknown operation: " << op << "\n"; std::cerr << "Unknown operation: " << op << "\n";
return EXIT_FAILURE; return EXIT_FAILURE;
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -28,9 +28,7 @@ struct VertexAttrib {
}; };
struct MeshPrim { struct PrimSet {
std::string name;
int32_t id{-1};
int mode; // e.g. TRIANGLES int mode; // e.g. TRIANGLES
@ -49,14 +47,35 @@ struct MeshPrim {
int indices_max{-1}; int indices_max{-1};
int indices_type{-1}; // storage type(componentType) of `indices`. int indices_type{-1}; // storage type(componentType) of `indices`.
std::vector<uint32_t> indices; // vertex indices std::vector<uint32_t> indices; // vertex indices
};
struct MeshPrim {
std::string name;
int32_t id{-1};
std::vector<PrimSet> prims;
};
struct ObjExportOption
{
bool export_skinweights{true};
int primid{0}; /// Primitive id to export(default 0).
int uvset{0}; /// Tex coord ID to export(default 0).
bool flip_texcoord_y{true}; /// Flip texture coordinate V?(default true).
}; };
/// ///
/// Save MeshPrim as wavefront .obj /// Save MeshPrim as wavefront .obj
/// ///
bool SaveAsObjMesh(const std::string &filename, const MeshPrim &mesh, bool flip_texcoord_y = true); /// @param[in] basename Base filename. ".obj" will be appended.
/// @param[in] mesh MeshPrim.
/// @param[in] option Export options
/// ///
bool SaveAsObjMesh(const std::string &basename, const MeshPrim &mesh, const ObjExportOption &option);
//
/// Save MeshPrim as glTF mesh /// Save MeshPrim as glTF mesh
/// ///
bool SaveAsGLTFMesh(const std::string &filename, const MeshPrim &mesh); bool SaveAsGLTFMesh(const std::string &filename, const MeshPrim &mesh);

View File

@ -0,0 +1,18 @@
# concat_mesh.py
Append(merge) mesh of glTF A to glTF B.
`meshes`, `accessors`, `bufferViews`, `materials` of glTF A is appended to glTF B(index to accessor, bufferViews, etc will be recomputed).
`skin`, `nodes`, etc are not appended(to be merged).
## TODO
* [ ] Support multiple glTFs to merge
* [ ] Support merging skin
* [ ] Support merging different node hierarchies
* [ ] `images`, `textures`
# replace_attrib.py
Replace the accessor id of specified attribute.

View File

@ -0,0 +1,110 @@
# concat mesh to glTF
import json
import sys, os
prefix = "added/"
def main():
if len(sys.argv) < 4:
print("Needs source.gltf target.gltf output.gltf")
sys.exit(-1)
source_filename = sys.argv[1]
target_filename = sys.argv[2]
output_filename = sys.argv[3]
source = json.loads(open(source_filename).read())
target = json.loads(open(target_filename).read())
num_target_meshes = len(target["meshes"])
num_target_buffers = len(target["buffers"])
num_target_bufferViews = len(target["bufferViews"])
num_target_accessors = len(target["accessors"])
num_target_materials = len(target["materials"])
print("num_target_meshes: ", num_target_meshes)
print("num_target_buffers: ", num_target_buffers)
print("num_target_bufferViews: ", num_target_bufferViews)
print("num_target_accessors: ", num_target_accessors)
print("num_target_materials: ", num_target_accessors)
num_source_meshes = len(source["meshes"])
num_source_buffers = len(source["buffers"])
num_source_bufferViews = len(source["bufferViews"])
num_source_accessors = len(source["accessors"])
num_source_materials = len(source["materials"]) if "materials" in source else 0
print("num_source_meshes: ", num_source_meshes)
print("num_source_buffers: ", num_source_buffers)
print("num_source_bufferViews: ", num_source_bufferViews)
print("num_source_accessors: ", num_source_accessors)
print("num_source_materials: ", num_source_materials)
#
# Adjust name and index
#
for i in range(len(source["buffers"])):
if "name" in source["buffers"][i]:
source["buffers"][i]["name"] = prefix + source["buffers"][i]["name"]
for i in range(len(source["bufferViews"])):
if "name" in source["bufferViews"][i]:
source["bufferViews"][i]["name"] = prefix + source["bufferViews"][i]["name"]
source["bufferViews"][i]["buffer"] += num_target_buffers
for i in range(len(source["accessors"])):
if "name" in source["accessors"][i]:
source["accessors"][i]["name"] = prefix + source["accessors"][i]["name"]
source["accessors"][i]["bufferView"] += num_target_bufferViews
for i in range(len(source["meshes"])):
mesh = source["meshes"][i]
if "name" in mesh:
source["meshes"][i]["name"] = prefix + source["meshes"][i]["name"]
for primid in range(len(mesh["primitives"])):
for attrib in mesh["primitives"][primid]["attributes"]:
#print(source["meshes"][i]["primitives"][primid]["attributes"][attrib])
source["meshes"][i]["primitives"][primid]["attributes"][attrib] += num_target_accessors
source["meshes"][i]["primitives"][primid]["indices"] += num_target_accessors
if "material" in source["meshes"][i]["primitives"][primid]:
source["meshes"][i]["primitives"][primid]["material"] += num_target_materials
#
# Append mesh info
#
target["buffers"] += source["buffers"]
target["bufferViews"] += source["bufferViews"]
target["meshes"] += source["meshes"]
target["accessors"] += source["accessors"]
if "materials" in source:
target["materials"] += source["materials"]
#
# add some info
#
extraInfo = {}
extraInfo["num_target_meshes"] = num_target_meshes
extraInfo["num_target_buffers"] = num_target_buffers
extraInfo["num_target_bufferViews"] = num_target_bufferViews
extraInfo["num_target_accessors"] = num_target_accessors
extraInfo["num_target_materials"] = num_target_materials
extraInfo["num_source_meshes"] = num_source_meshes
extraInfo["num_source_buffers"] = num_source_buffers
extraInfo["num_source_bufferViews"] = num_source_bufferViews
extraInfo["num_source_accessors"] = num_source_accessors
extraInfo["num_source_materials"] = num_source_materials
target["asset"]["extras"] = extraInfo
with open(output_filename, "w") as f:
f.write(json.dumps(target, indent=2))
print("Merged glTF was exported to : ", output_filename)
main()

View File

@ -0,0 +1,97 @@
# Replace accessor id of attributes for speicified mesh
# Usually called after concat_mesh.py
# Example usecase is to replace UV coordinate of a mesh.
import json
import sys, os
attrib_names = ["TEXCOORD_0"]
def check_accessor(src, target):
if src["componentType"] != target["componentType"]:
print("componentType mismatch!")
return False
if src["count"] != target["count"]:
print("`count` mismatch!")
return False
if src["type"] != target["type"]:
print("`type` mismatch!")
return False
return True
def main():
if len(sys.argv) < 5:
print("Needs input.gltf output.gltf source_mesh_name target_mesh_name <source_primid> <target_primid>")
sys.exit(-1)
input_filename = sys.argv[1]
output_filename = sys.argv[2]
source_mesh_name = sys.argv[3]
target_mesh_name = sys.argv[4]
source_primid = 0
target_primid = 0
if len(sys.argv) > 5:
source_primid = int(sys.argv[5])
if len(sys.argv) > 6:
target_primid = int(sys.argv[6])
gltf = json.loads(open(input_filename).read())
source_mesh_id = -1
target_mesh_id = -1
for i in range(len(gltf["meshes"])):
mesh = gltf["meshes"][i]
print("mesh[{}].name = {}".format(i, mesh["name"]))
if target_mesh_name == mesh["name"]:
target_mesh_id = i
if source_mesh_name == mesh["name"]:
source_mesh_id = i
if source_mesh_id == -1:
print("source mesh with name [{}] not found.".format(source_mesh_name))
sys.exit(-1)
if target_mesh_id == -1:
print("target mesh with name [{}] not found.".format(target_mesh_name))
sys.exit(-1)
print("target: name = {}, id = {}".format(target_mesh_name, target_mesh_id))
print("source: name = {}, id = {}".format(source_mesh_name, source_mesh_id))
source_mesh = gltf["meshes"][source_mesh_id]
target_mesh = gltf["meshes"][target_mesh_id]
source_prim = source_mesh["primitives"][source_primid]
target_prim = target_mesh["primitives"][target_primid]
for attrib in target_prim["attributes"]:
print("attrib ", attrib)
if attrib in attrib_names:
if attrib in source_prim["attributes"]:
target_accessor_id = target_prim["attributes"][attrib]
src_accessor_id = source_prim["attributes"][attrib]
if check_accessor(gltf["accessors"][src_accessor_id], gltf["accessors"][target_accessor_id]):
gltf["meshes"][target_mesh_id]["primitives"][target_primid]["attributes"][attrib] = src_accessor_id
print("Replaced accessor id for attrib {} from {} to {}".format(attrib, target_accessor_id, src_accessor_id))
else:
print("Accessor type/format is not identical. Skip replace")
print(" attrib {}".format(attrib))
else:
print("attribute[{}] not found in source primitive: mesh[{}].primitives[{}]".format(attrib, source_mesh_name, source_primid))
with open(output_filename, "w") as f:
f.write(json.dumps(gltf, indent=2))
print("Merged glTF was exported to : ", output_filename)
main()