mirror of
https://github.com/syoyo/tinygltf.git
synced 2026-06-13 18:58:53 +00:00
Compare commits
11 Commits
textureinf
...
rapidjson
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c0bcb7d72 | ||
|
|
d5694dc15d | ||
|
|
ddf0a0e83c | ||
|
|
f3ee08c595 | ||
|
|
f75327bc5b | ||
|
|
7acc95a2ae | ||
|
|
2a6f2cc356 | ||
|
|
0cf2812775 | ||
|
|
2673d1d3ef | ||
|
|
cf8b7cc0a4 | ||
|
|
93d0e365bb |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -67,5 +67,4 @@ imgui.ini
|
||||
loader_example
|
||||
tests/tester
|
||||
tests/tester_noexcept
|
||||
tests/issue-97.gltf
|
||||
|
||||
|
||||
@@ -42,10 +42,6 @@ script:
|
||||
- ${CC} -v
|
||||
- ${CXX} ${EXTRA_CXXFLAGS} -std=c++11 -Wall -g -o loader_example loader_example.cc
|
||||
- ./loader_example ./models/Cube/Cube.gltf
|
||||
- cd tests
|
||||
- make
|
||||
- ./tester
|
||||
- ./tester_noexcept
|
||||
- cd ../examples/raytrace
|
||||
- cd examples/raytrace
|
||||
- ../../premake5 gmake
|
||||
- make
|
||||
|
||||
13
LICENSE.rapidjson.txt
Normal file
13
LICENSE.rapidjson.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
// Tencent is pleased to support the open source community by making RapidJSON available.
|
||||
//
|
||||
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved.
|
||||
//
|
||||
// Licensed under the MIT License (the "License"); you may not use this file except
|
||||
// in compliance with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software distributed
|
||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
28
README.md
28
README.md
@@ -2,12 +2,11 @@
|
||||
|
||||
`TinyGLTF` is a header only C++11 glTF 2.0 https://github.com/KhronosGroup/glTF library.
|
||||
|
||||
`TinyGLTF` uses Niels Lohmann's json library(https://github.com/nlohmann/json), so now it requires C++11 compiler.
|
||||
If you are looking for old, C++03 version, please use `devel-picojson` branch.
|
||||
`TinyGLTF` uses single-header version of RapidJson(https://github.com/Tencent/rapidjson) as a JSON parser.
|
||||
|
||||
## Status
|
||||
|
||||
- v2.3.0 Modified Material representation according to glTF 2.0 schema(and introduced TextureInfo class)
|
||||
- v2.3.0 release(Support built-in schema validation)
|
||||
- v2.2.0 release(Support loading 16bit PNG. Sparse accessor support)
|
||||
- v2.1.0 release(Draco support)
|
||||
- v2.0.0 release(22 Aug, 2018)!
|
||||
@@ -35,6 +34,7 @@ If you are looking for old, C++03 version, please use `devel-picojson` branch.
|
||||
* [x] ASCII glTF
|
||||
* [x] Binary glTF(GLB)
|
||||
* [x] PBR material description
|
||||
* [x] Validate inpit glTF JSON with schema
|
||||
* Buffers
|
||||
* [x] Parse BASE64 encoded embedded buffer data(DataURI).
|
||||
* [x] Load `.bin` file.
|
||||
@@ -55,12 +55,6 @@ If you are looking for old, C++03 version, please use `devel-picojson` branch.
|
||||
* Extensions
|
||||
* [x] Draco mesh decoding
|
||||
|
||||
## Note on extension property
|
||||
|
||||
In extension(`ExtensionMap`), JSON number value is parsed as int or float(number) and stored as `tinygltf::Value` object. If you want a floating point value from `tinygltf::Value`, use `GetNumberAsDouble()` method.
|
||||
|
||||
`IsNumber()` returns true if the underlying value is an int value or a floating point value.
|
||||
|
||||
## Examples
|
||||
|
||||
* [glview](examples/glview) : Simple glTF geometry viewer.
|
||||
@@ -73,9 +67,13 @@ In extension(`ExtensionMap`), JSON number value is parsed as int or float(number
|
||||
* Physical based rendering with Vulkan using glTF 2.0 models https://github.com/SaschaWillems/Vulkan-glTF-PBR
|
||||
* GLTF loader plugin for OGRE 2.1. Support for PBR materials via HLMS/PBS https://github.com/Ybalrid/Ogre_glTF
|
||||
* [TinyGltfImporter](http://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1TinyGltfImporter.html) plugin for [Magnum](https://github.com/mosra/magnum), a lightweight and modular C++11/C++14 graphics middleware for games and data visualization.
|
||||
* [Diligent Engine](https://github.com/DiligentGraphics/DiligentEngine) - A modern cross-platform low-level graphics library and rendering framework
|
||||
* Your projects here! (Please send PR)
|
||||
|
||||
## For developer
|
||||
|
||||
Generate single rapidjson file using this node.js script: https://github.com/Tencent/rapidjson/issues/863
|
||||
Add `cursorstreamwrapper.h` and `error/en.h` inclusion in `rapidjson-all.h` before running merge script.
|
||||
|
||||
## TODOs
|
||||
|
||||
* [ ] Write C++ code generator which emits C++ code from JSON schema for robust parsing.
|
||||
@@ -83,7 +81,7 @@ In extension(`ExtensionMap`), JSON number value is parsed as int or float(number
|
||||
* [x] Load Draco compressed mesh
|
||||
* [ ] Save Draco compressed mesh
|
||||
* [ ] Open3DGC?
|
||||
* [x] Support `extensions` and `extras` property
|
||||
* [ ] Support `extensions` and `extras` property
|
||||
* [ ] HDR image?
|
||||
* [ ] OpenEXR extension through TinyEXR.
|
||||
* [ ] 16bit PNG support in Serialization
|
||||
@@ -103,7 +101,7 @@ TinyGLTF uses the following third party libraries.
|
||||
|
||||
## Build and example
|
||||
|
||||
Copy `stb_image.h`, `stb_image_write.h`, `json.hpp` and `tiny_gltf.h` to your project.
|
||||
Copy `stb_image.h`, `stb_image_write.h`, `rapidjson-amalgamation.h` and `tiny_gltf.h` to your project.
|
||||
|
||||
### Loading glTF 2.0 model
|
||||
|
||||
@@ -112,6 +110,7 @@ Copy `stb_image.h`, `stb_image_write.h`, `json.hpp` and `tiny_gltf.h` to your pr
|
||||
#define TINYGLTF_IMPLEMENTATION
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
// #define TINYGLTF_ENABLE_SCHEMA_VALIDATOR // optional. enable schema validation API.
|
||||
// #define TINYGLTF_NOEXCEPTION // optional. disable exception handling.
|
||||
#include "tiny_gltf.h"
|
||||
|
||||
@@ -124,6 +123,10 @@ std::string warn;
|
||||
|
||||
bool ret = loader.LoadASCIIFromFile(&model, &err, &warn, argv[1]);
|
||||
//bool ret = loader.LoadBinaryFromFile(&model, &err, &warn, argv[1]); // for binary glTF(.glb)
|
||||
// Validate with glTF Schema
|
||||
// #if defined(TINYGLTF_ENABLE_SCHEMA_VALIDATOR)
|
||||
// bool ret = loader.LoadASCIIFromFileWithValidation(&model, &err, &warn, argv[1]);
|
||||
// #endif
|
||||
|
||||
if (!warn.empty()) {
|
||||
printf("Warn: %s\n", warn.c_str());
|
||||
@@ -141,6 +144,7 @@ if (!ret) {
|
||||
|
||||
## Compile options
|
||||
|
||||
* `TINYGLTF_ENABLE_SCHEMA_VALIDATOR` : Enable API with schema validation. glTF Schema JSON(`gltf.schema.resolved.inc`) are embeded into application binary, thus no need to read glTF Schema file at a runtime.
|
||||
* `TINYGLTF_NOEXCEPTION` : Disable C++ exception in JSON parsing. You can use `-fno-exceptions` or by defining the symbol `JSON_NOEXCEPTION` and `TINYGLTF_NOEXCEPTION` to fully remove C++ exception codes when compiling TinyGLTF.
|
||||
* `TINYGLTF_NO_STB_IMAGE` : Do not load images with stb_image. Instead use `TinyGLTF::SetImageLoader(LoadimageDataFunction LoadImageData, void *user_data)` to set a callback for loading images.
|
||||
* `TINYGLTF_NO_STB_IMAGE_WRITE` : Do not write images with stb_image_write. Instead use `TinyGLTF::SetImageWriter(WriteimageDataFunction WriteImageData, void *user_data)` to set a callback for writing images.
|
||||
|
||||
16
gltf.schema.resolved.inc
Normal file
16
gltf.schema.resolved.inc
Normal file
File diff suppressed because one or more lines are too long
@@ -2,6 +2,7 @@
|
||||
// TODO(syoyo): Print extensions and extras for each glTF object.
|
||||
//
|
||||
#define TINYGLTF_IMPLEMENTATION
|
||||
#define TINYGLTF_ENABLE_SCHEMA_VALIDATOR
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#include "tiny_gltf.h"
|
||||
@@ -175,10 +176,7 @@ static std::string PrintIntArray(const std::vector<int> &arr) {
|
||||
std::stringstream ss;
|
||||
ss << "[ ";
|
||||
for (size_t i = 0; i < arr.size(); i++) {
|
||||
ss << arr[i];
|
||||
if (i != arr.size() - 1) {
|
||||
ss << ", ";
|
||||
}
|
||||
ss << arr[i] << ((i != arr.size() - 1) ? ", " : "");
|
||||
}
|
||||
ss << " ]";
|
||||
|
||||
@@ -193,10 +191,7 @@ static std::string PrintFloatArray(const std::vector<double> &arr) {
|
||||
std::stringstream ss;
|
||||
ss << "[ ";
|
||||
for (size_t i = 0; i < arr.size(); i++) {
|
||||
ss << arr[i];
|
||||
if (i != arr.size() - 1) {
|
||||
ss << ", ";
|
||||
}
|
||||
ss << arr[i] << ((i != arr.size() - 1) ? ", " : "");
|
||||
}
|
||||
ss << " ]";
|
||||
|
||||
@@ -249,36 +244,35 @@ static std::string PrintValue(const std::string &name,
|
||||
if (tag) {
|
||||
ss << Indent(indent) << name << " : " << value.Get<std::string>();
|
||||
} else {
|
||||
ss << Indent(indent) << value.Get<std::string>() << " ";
|
||||
ss << " " << value.Get<std::string>() << " ";
|
||||
}
|
||||
} else if (value.IsBool()) {
|
||||
if (tag) {
|
||||
ss << Indent(indent) << name << " : " << value.Get<bool>();
|
||||
} else {
|
||||
ss << Indent(indent) << value.Get<bool>() << " ";
|
||||
ss << " " << value.Get<bool>() << " ";
|
||||
}
|
||||
} else if (value.IsNumber()) {
|
||||
if (tag) {
|
||||
ss << Indent(indent) << name << " : " << value.Get<double>();
|
||||
} else {
|
||||
ss << Indent(indent) << value.Get<double>() << " ";
|
||||
ss << " " << value.Get<double>() << " ";
|
||||
}
|
||||
} else if (value.IsInt()) {
|
||||
if (tag) {
|
||||
ss << Indent(indent) << name << " : " << value.Get<int>();
|
||||
} else {
|
||||
ss << Indent(indent) << value.Get<int>() << " ";
|
||||
ss << " " << value.Get<int>() << " ";
|
||||
}
|
||||
} else if (value.IsArray()) {
|
||||
// TODO(syoyo): Better pretty printing of array item
|
||||
ss << Indent(indent) << name << " [ \n";
|
||||
ss << Indent(indent) << name << " [ ";
|
||||
for (size_t i = 0; i < value.Size(); i++) {
|
||||
ss << PrintValue("", value.Get(int(i)), indent + 1, /* tag */ false);
|
||||
if (i != (value.ArrayLen() - 1)) {
|
||||
ss << ", \n";
|
||||
ss << ", ";
|
||||
}
|
||||
}
|
||||
ss << "\n" << Indent(indent) << "] ";
|
||||
ss << Indent(indent) << "] ";
|
||||
}
|
||||
|
||||
// @todo { binary }
|
||||
@@ -346,54 +340,6 @@ static void DumpExtensions(const tinygltf::ExtensionMap &extension,
|
||||
}
|
||||
}
|
||||
|
||||
static void DumpTextureInfo(const tinygltf::TextureInfo &texinfo,
|
||||
const int indent) {
|
||||
std::cout << Indent(indent) << "index : " << texinfo.index << "\n";
|
||||
std::cout << Indent(indent) << "texCoord : TEXCOORD_" << texinfo.texCoord
|
||||
<< "\n";
|
||||
DumpExtensions(texinfo.extensions, indent + 1);
|
||||
std::cout << PrintValue("extras", texinfo.extras, indent + 1) << "\n";
|
||||
}
|
||||
|
||||
static void DumpNormalTextureInfo(const tinygltf::NormalTextureInfo &texinfo,
|
||||
const int indent) {
|
||||
std::cout << Indent(indent) << "index : " << texinfo.index << "\n";
|
||||
std::cout << Indent(indent) << "texCoord : TEXCOORD_" << texinfo.texCoord
|
||||
<< "\n";
|
||||
std::cout << Indent(indent) << "scale : " << texinfo.scale << "\n";
|
||||
DumpExtensions(texinfo.extensions, indent + 1);
|
||||
std::cout << PrintValue("extras", texinfo.extras, indent + 1) << "\n";
|
||||
}
|
||||
|
||||
static void DumpOcclusionTextureInfo(
|
||||
const tinygltf::OcclusionTextureInfo &texinfo, const int indent) {
|
||||
std::cout << Indent(indent) << "index : " << texinfo.index << "\n";
|
||||
std::cout << Indent(indent) << "texCoord : TEXCOORD_" << texinfo.texCoord
|
||||
<< "\n";
|
||||
std::cout << Indent(indent) << "strength : " << texinfo.strength << "\n";
|
||||
DumpExtensions(texinfo.extensions, indent + 1);
|
||||
std::cout << PrintValue("extras", texinfo.extras, indent + 1) << "\n";
|
||||
}
|
||||
|
||||
static void DumpPbrMetallicRoughness(const tinygltf::PbrMetallicRoughness &pbr,
|
||||
const int indent) {
|
||||
std::cout << Indent(indent)
|
||||
<< "baseColorFactor : " << PrintFloatArray(pbr.baseColorFactor)
|
||||
<< "\n";
|
||||
std::cout << Indent(indent) << "baseColorTexture :\n";
|
||||
DumpTextureInfo(pbr.baseColorTexture, indent + 1);
|
||||
|
||||
std::cout << Indent(indent) << "metallicFactor : " << pbr.metallicFactor
|
||||
<< "\n";
|
||||
std::cout << Indent(indent) << "roughnessFactor : " << pbr.roughnessFactor
|
||||
<< "\n";
|
||||
|
||||
std::cout << Indent(indent) << "metallicRoughnessTexture :\n";
|
||||
DumpTextureInfo(pbr.metallicRoughnessTexture, indent + 1);
|
||||
DumpExtensions(pbr.extensions, indent + 1);
|
||||
std::cout << PrintValue("extras", pbr.extras, indent + 1) << "\n";
|
||||
}
|
||||
|
||||
static void Dump(const tinygltf::Model &model) {
|
||||
std::cout << "=== Dump glTF ===" << std::endl;
|
||||
std::cout << "asset.copyright : " << model.asset.copyright
|
||||
@@ -564,44 +510,16 @@ static void Dump(const tinygltf::Model &model) {
|
||||
<< std::endl;
|
||||
for (size_t i = 0; i < model.materials.size(); i++) {
|
||||
const tinygltf::Material &material = model.materials[i];
|
||||
std::cout << Indent(1) << "name : " << material.name
|
||||
<< std::endl;
|
||||
|
||||
std::cout << Indent(1) << "alphaMode : " << material.alphaMode
|
||||
<< std::endl;
|
||||
std::cout << Indent(1)
|
||||
<< "alphaCutoff : " << material.alphaCutoff
|
||||
<< std::endl;
|
||||
std::cout << Indent(1) << "doubleSided : "
|
||||
<< (material.doubleSided ? "true" : "false") << std::endl;
|
||||
std::cout << Indent(1) << "emissiveFactor : "
|
||||
<< PrintFloatArray(material.emissiveFactor) << std::endl;
|
||||
|
||||
std::cout << Indent(1) << "pbrMetallicRoughness :\n";
|
||||
DumpPbrMetallicRoughness(material.pbrMetallicRoughness, 2);
|
||||
|
||||
std::cout << Indent(1) << "normalTexture :\n";
|
||||
DumpNormalTextureInfo(material.normalTexture, 2);
|
||||
|
||||
std::cout << Indent(1) << "occlusionTexture :\n";
|
||||
DumpOcclusionTextureInfo(material.occlusionTexture, 2);
|
||||
|
||||
std::cout << Indent(1) << "emissiveTexture :\n";
|
||||
DumpTextureInfo(material.emissiveTexture, 2);
|
||||
|
||||
std::cout << Indent(1) << "---- legacy material parameter ----\n";
|
||||
std::cout << Indent(1) << "name : " << material.name << std::endl;
|
||||
std::cout << Indent(1) << "values(items=" << material.values.size() << ")"
|
||||
<< std::endl;
|
||||
|
||||
tinygltf::ParameterMap::const_iterator p(material.values.begin());
|
||||
tinygltf::ParameterMap::const_iterator pEnd(material.values.end());
|
||||
for (; p != pEnd; p++) {
|
||||
std::cout << Indent(2) << p->first << ": "
|
||||
<< PrintParameterValue(p->second) << std::endl;
|
||||
}
|
||||
std::cout << Indent(1) << "-------------------------------------\n";
|
||||
|
||||
DumpExtensions(material.extensions, 1);
|
||||
std::cout << PrintValue("extras", material.extras, 2) << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -723,8 +641,13 @@ int main(int argc, char **argv) {
|
||||
} else {
|
||||
std::cout << "Reading ASCII glTF" << std::endl;
|
||||
// assume ascii glTF.
|
||||
#if defined(TINYGLTF_ENABLE_SCHEMA_VALIDATOR)
|
||||
ret =
|
||||
gltf_ctx.LoadASCIIFromFileWithValidation(&model, &err, &warn, input_filename.c_str());
|
||||
#else
|
||||
ret =
|
||||
gltf_ctx.LoadASCIIFromFile(&model, &err, &warn, input_filename.c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!warn.empty()) {
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
{
|
||||
"scenes": [
|
||||
{
|
||||
"nodes": [0]
|
||||
}
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"mesh": 0
|
||||
}
|
||||
],
|
||||
"meshes": [
|
||||
{
|
||||
"primitives": [
|
||||
{
|
||||
"attributes": {
|
||||
"POSITION": 1
|
||||
},
|
||||
"indices": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"buffers": [
|
||||
{
|
||||
"uri": "simpleTriangle.bin",
|
||||
"byteLength": 44
|
||||
}
|
||||
],
|
||||
"bufferViews": [
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteOffset": 0,
|
||||
"byteLength": 1e300,
|
||||
"target": 34963
|
||||
},
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteOffset": 8,
|
||||
"byteLength": 36,
|
||||
"target": 34962
|
||||
}
|
||||
],
|
||||
"accessors": [
|
||||
{
|
||||
"bufferView": 0,
|
||||
"byteOffset": 0,
|
||||
"componentType": 5123,
|
||||
"count": 3,
|
||||
"type": "SCALAR",
|
||||
"max": [2],
|
||||
"min": [0]
|
||||
},
|
||||
{
|
||||
"bufferView": 1,
|
||||
"byteOffset": 0,
|
||||
"componentType": 5126,
|
||||
"count": 3,
|
||||
"type": "VEC3",
|
||||
"max": [1, 1, 0],
|
||||
"min": [0, 0, 0]
|
||||
}
|
||||
],
|
||||
"asset": {
|
||||
"version": "2.0"
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
{
|
||||
"scenes": [],
|
||||
"nodes": [],
|
||||
"meshes": [
|
||||
{
|
||||
"primitives": [
|
||||
{
|
||||
"attributes": {},
|
||||
"indices": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"buffers": [
|
||||
{
|
||||
"uri": "simpleTriangle.bin",
|
||||
"byteLength": 44
|
||||
}
|
||||
],
|
||||
"bufferViews": [
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteOffset": 0,
|
||||
"byteLength": 6,
|
||||
"target": 34963
|
||||
},
|
||||
{
|
||||
"buffer": 1,
|
||||
"byteOffset": 0,
|
||||
"byteLength": 6,
|
||||
"target": 34963
|
||||
}
|
||||
],
|
||||
"images": [
|
||||
{
|
||||
"bufferView": 1,
|
||||
"mimeType": "image/png"
|
||||
}
|
||||
],
|
||||
"accessors": [
|
||||
{
|
||||
"bufferView": 0,
|
||||
"componentType": 5123,
|
||||
"count": 3,
|
||||
"type": "SCALAR",
|
||||
"max": [2],
|
||||
"min": [0]
|
||||
}
|
||||
],
|
||||
"asset": {
|
||||
"version": "2.0"
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"scenes": [],
|
||||
"nodes": [],
|
||||
"buffers": [],
|
||||
"meshes": [
|
||||
{
|
||||
"primitives": [
|
||||
{
|
||||
"attributes": {},
|
||||
"indices": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"bufferViews": [
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteOffset": 0,
|
||||
"byteLength": 6,
|
||||
"target": 34963
|
||||
}
|
||||
],
|
||||
"accessors": [
|
||||
{
|
||||
"bufferView": 1,
|
||||
"componentType": 5123,
|
||||
"count": 3,
|
||||
"type": "SCALAR",
|
||||
"max": [2],
|
||||
"min": [0]
|
||||
}
|
||||
],
|
||||
"asset": {
|
||||
"version": "2.0"
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"scenes": [],
|
||||
"nodes": [],
|
||||
"buffers": [],
|
||||
"meshes": [
|
||||
{
|
||||
"primitives": [
|
||||
{
|
||||
"attributes": {},
|
||||
"indices": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"bufferViews": [
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteOffset": 0,
|
||||
"byteLength": 6,
|
||||
"target": 34963
|
||||
}
|
||||
],
|
||||
"accessors": [
|
||||
{
|
||||
"bufferView": 1,
|
||||
"componentType": 5123,
|
||||
"count": 3,
|
||||
"type": "SCALAR",
|
||||
"max": [2],
|
||||
"min": [0]
|
||||
}
|
||||
],
|
||||
"asset": {
|
||||
"version": "2.0"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 44 B |
@@ -1,224 +0,0 @@
|
||||
{
|
||||
"accessors": [
|
||||
{
|
||||
"bufferView": 0,
|
||||
"byteOffset": 0,
|
||||
"componentType": 5123,
|
||||
"count": 36,
|
||||
"max": [
|
||||
35
|
||||
],
|
||||
"min": [
|
||||
0
|
||||
],
|
||||
"type": "SCALAR"
|
||||
},
|
||||
{
|
||||
"bufferView": 1,
|
||||
"byteOffset": 0,
|
||||
"componentType": 5126,
|
||||
"count": 36,
|
||||
"max": [
|
||||
1,
|
||||
1,
|
||||
1.000001
|
||||
],
|
||||
"min": [
|
||||
-1,
|
||||
-1,
|
||||
-1
|
||||
],
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 2,
|
||||
"byteOffset": 0,
|
||||
"componentType": 5126,
|
||||
"count": 36,
|
||||
"max": [
|
||||
1,
|
||||
1,
|
||||
1
|
||||
],
|
||||
"min": [
|
||||
-1,
|
||||
-1,
|
||||
-1
|
||||
],
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 3,
|
||||
"byteOffset": 0,
|
||||
"componentType": 5126,
|
||||
"count": 36,
|
||||
"max": [
|
||||
1,
|
||||
-0,
|
||||
-0,
|
||||
1
|
||||
],
|
||||
"min": [
|
||||
0,
|
||||
-0,
|
||||
-1,
|
||||
-1
|
||||
],
|
||||
"type": "VEC4"
|
||||
},
|
||||
{
|
||||
"bufferView": 4,
|
||||
"byteOffset": 0,
|
||||
"componentType": 5126,
|
||||
"count": 36,
|
||||
"max": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"min": [
|
||||
-1,
|
||||
-1
|
||||
],
|
||||
"type": "VEC2"
|
||||
}
|
||||
],
|
||||
"asset": {
|
||||
"generator": "VKTS glTF 2.0 exporter",
|
||||
"version": "2.0"
|
||||
},
|
||||
"bufferViews": [
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteLength": 72,
|
||||
"byteOffset": 0,
|
||||
"target": 34963
|
||||
},
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteLength": 432,
|
||||
"byteOffset": 72,
|
||||
"target": 34962
|
||||
},
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteLength": 432,
|
||||
"byteOffset": 504,
|
||||
"target": 34962
|
||||
},
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteLength": 576,
|
||||
"byteOffset": 936,
|
||||
"target": 34962
|
||||
},
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteLength": 288,
|
||||
"byteOffset": 1512,
|
||||
"target": 34962
|
||||
}
|
||||
],
|
||||
"buffers": [
|
||||
{
|
||||
"byteLength": 1800,
|
||||
"uri": "Cube.bin"
|
||||
}
|
||||
],
|
||||
"images": [
|
||||
{
|
||||
"0comment": "Use Cube_MetallicRoughness.png to reduce scene filesize",
|
||||
"uri": "Cube_MetallicRoughness.png"
|
||||
},
|
||||
{
|
||||
"uri": "Cube_MetallicRoughness.png"
|
||||
}
|
||||
],
|
||||
"materials": [
|
||||
{
|
||||
"emissiveTexture": {
|
||||
"index": 0,
|
||||
"extensions": {
|
||||
"KHR_texture_transform": {
|
||||
"offset": [
|
||||
0,
|
||||
1
|
||||
],
|
||||
"scale": [
|
||||
1,
|
||||
-1
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Cube",
|
||||
"pbrMetallicRoughness": {
|
||||
"baseColorTexture": {
|
||||
"index": 0
|
||||
},
|
||||
"metallicRoughnessTexture": {
|
||||
"index": 1,
|
||||
"extensions": {
|
||||
"KHR_texture_transform": {
|
||||
"offset": [
|
||||
0,
|
||||
1
|
||||
],
|
||||
"rotation": 1.57079632679,
|
||||
"scale": [
|
||||
0.5,
|
||||
0.5
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"meshes": [
|
||||
{
|
||||
"name": "Cube",
|
||||
"primitives": [
|
||||
{
|
||||
"attributes": {
|
||||
"NORMAL": 2,
|
||||
"POSITION": 1,
|
||||
"TANGENT": 3,
|
||||
"TEXCOORD_0": 4
|
||||
},
|
||||
"indices": 0,
|
||||
"material": 0,
|
||||
"mode": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"mesh": 0,
|
||||
"name": "Cube"
|
||||
}
|
||||
],
|
||||
"samplers": [
|
||||
{}
|
||||
],
|
||||
"scene": 0,
|
||||
"scenes": [
|
||||
{
|
||||
"nodes": [
|
||||
0
|
||||
]
|
||||
}
|
||||
],
|
||||
"textures": [
|
||||
{
|
||||
"sampler": 0,
|
||||
"source": 0
|
||||
},
|
||||
{
|
||||
"sampler": 0,
|
||||
"source": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 319 B |
@@ -1,6 +0,0 @@
|
||||
Added KHR_texture_transform property to Cube scene.
|
||||
|
||||
License: Donated by Norbert Nopper for glTF testing.
|
||||
|
||||
https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/Cube
|
||||
|
||||
16906
rapidjson-amalgamation.h
Normal file
16906
rapidjson-amalgamation.h
Normal file
File diff suppressed because it is too large
Load Diff
233
tests/tester.cc
233
tests/tester.cc
@@ -37,7 +37,7 @@ TEST_CASE("datauri-in-glb", "[issue-79]") {
|
||||
if (!err.empty()) {
|
||||
std::cerr << err << std::endl;
|
||||
}
|
||||
|
||||
|
||||
REQUIRE(true == ret);
|
||||
}
|
||||
|
||||
@@ -82,236 +82,7 @@ TEST_CASE("extension-with-empty-object", "[issue-97]") {
|
||||
REQUIRE(m.materials[0].extensions.size() == 1);
|
||||
REQUIRE(m.materials[0].extensions.count("VENDOR_material_some_ext") == 1);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE("invalid-primitive-indices", "[bounds-checking]") {
|
||||
tinygltf::Model model;
|
||||
tinygltf::TinyGLTF ctx;
|
||||
std::string err;
|
||||
std::string warn;
|
||||
|
||||
// Loading is expected to fail, but not crash.
|
||||
bool ret = ctx.LoadASCIIFromFile(
|
||||
&model, &err, &warn,
|
||||
"../models/BoundsChecking/invalid-primitive-indices.gltf");
|
||||
REQUIRE_THAT(err,
|
||||
Catch::Contains("primitive indices accessor out of bounds"));
|
||||
REQUIRE_FALSE(ret);
|
||||
}
|
||||
|
||||
TEST_CASE("invalid-buffer-view-index", "[bounds-checking]") {
|
||||
tinygltf::Model model;
|
||||
tinygltf::TinyGLTF ctx;
|
||||
std::string err;
|
||||
std::string warn;
|
||||
|
||||
// Loading is expected to fail, but not crash.
|
||||
bool ret = ctx.LoadASCIIFromFile(
|
||||
&model, &err, &warn,
|
||||
"../models/BoundsChecking/invalid-buffer-view-index.gltf");
|
||||
REQUIRE_THAT(err, Catch::Contains("accessor[0] invalid bufferView"));
|
||||
REQUIRE_FALSE(ret);
|
||||
}
|
||||
|
||||
TEST_CASE("invalid-buffer-index", "[bounds-checking]") {
|
||||
tinygltf::Model model;
|
||||
tinygltf::TinyGLTF ctx;
|
||||
std::string err;
|
||||
std::string warn;
|
||||
|
||||
// Loading is expected to fail, but not crash.
|
||||
bool ret = ctx.LoadASCIIFromFile(
|
||||
&model, &err, &warn,
|
||||
"../models/BoundsChecking/invalid-buffer-index.gltf");
|
||||
REQUIRE_THAT(
|
||||
err, Catch::Contains("image[0] buffer \"1\" not found in the scene."));
|
||||
REQUIRE_FALSE(ret);
|
||||
}
|
||||
|
||||
TEST_CASE("glb-invalid-length", "[bounds-checking]") {
|
||||
tinygltf::Model model;
|
||||
tinygltf::TinyGLTF ctx;
|
||||
std::string err;
|
||||
std::string warn;
|
||||
|
||||
// This glb has a much longer length than the provided data and should fail
|
||||
// initial range checks.
|
||||
const unsigned char glb_invalid_length[] = "glTF"
|
||||
"\x20\x00\x00\x00" "\x6c\x66\x00\x00" //
|
||||
// | version | length |
|
||||
"\x02\x00\x00\x00" "\x4a\x53\x4f\x4e{}"; //
|
||||
// | model length | model format |
|
||||
|
||||
bool ret = ctx.LoadBinaryFromMemory(&model, &err, &warn, glb_invalid_length,
|
||||
sizeof(glb_invalid_length));
|
||||
REQUIRE_THAT(err, Catch::Contains("Invalid glTF binary."));
|
||||
REQUIRE_FALSE(ret);
|
||||
}
|
||||
|
||||
TEST_CASE("integer-out-of-bounds", "[bounds-checking]") {
|
||||
tinygltf::Model model;
|
||||
tinygltf::TinyGLTF ctx;
|
||||
std::string err;
|
||||
std::string warn;
|
||||
|
||||
// Loading is expected to fail, but not crash.
|
||||
bool ret = ctx.LoadASCIIFromFile(
|
||||
&model, &err, &warn,
|
||||
"../models/BoundsChecking/integer-out-of-bounds.gltf");
|
||||
REQUIRE_THAT(err, Catch::Contains("not a positive integer"));
|
||||
REQUIRE_FALSE(ret);
|
||||
}
|
||||
|
||||
TEST_CASE("parse-integer", "[bounds-checking]") {
|
||||
SECTION("parses valid numbers") {
|
||||
std::string err;
|
||||
int result = 123;
|
||||
CHECK(tinygltf::ParseIntegerProperty(&result, &err, {{"zero", 0}}, "zero",
|
||||
true));
|
||||
REQUIRE(err == "");
|
||||
REQUIRE(result == 0);
|
||||
|
||||
CHECK(tinygltf::ParseIntegerProperty(&result, &err, {{"int", -1234}}, "int",
|
||||
true));
|
||||
REQUIRE(err == "");
|
||||
REQUIRE(result == -1234);
|
||||
}
|
||||
|
||||
SECTION("detects missing properties") {
|
||||
std::string err;
|
||||
int result = -1;
|
||||
CHECK_FALSE(tinygltf::ParseIntegerProperty(&result, &err, {}, "int", true));
|
||||
REQUIRE_THAT(err, Catch::Contains("'int' property is missing"));
|
||||
REQUIRE(result == -1);
|
||||
}
|
||||
|
||||
SECTION("handled missing but not required properties") {
|
||||
std::string err;
|
||||
int result = -1;
|
||||
CHECK_FALSE(
|
||||
tinygltf::ParseIntegerProperty(&result, &err, {}, "int", false));
|
||||
REQUIRE(err == "");
|
||||
REQUIRE(result == -1);
|
||||
}
|
||||
|
||||
SECTION("invalid integers") {
|
||||
std::string err;
|
||||
int result = -1;
|
||||
|
||||
CHECK_FALSE(tinygltf::ParseIntegerProperty(&result, &err, {{"int", 0.5}},
|
||||
"int", true));
|
||||
REQUIRE_THAT(err, Catch::Contains("not an integer type"));
|
||||
|
||||
// Excessively large values and NaN aren't allowed either.
|
||||
err.clear();
|
||||
CHECK_FALSE(tinygltf::ParseIntegerProperty(&result, &err, {{"int", 1e300}},
|
||||
"int", true));
|
||||
REQUIRE_THAT(err, Catch::Contains("not an integer type"));
|
||||
|
||||
err.clear();
|
||||
CHECK_FALSE(tinygltf::ParseIntegerProperty(
|
||||
&result, &err, {{"int", std::numeric_limits<double>::quiet_NaN()}},
|
||||
"int", true));
|
||||
REQUIRE_THAT(err, Catch::Contains("not an integer type"));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("parse-unsigned", "[bounds-checking]") {
|
||||
SECTION("parses valid unsigned integers") {
|
||||
// Use string-based parsing here, using the initializer list syntax doesn't
|
||||
// parse 0 as unsigned.
|
||||
json zero_obj = json::parse("{\"zero\": 0}");
|
||||
|
||||
std::string err;
|
||||
size_t result = 123;
|
||||
CHECK(
|
||||
tinygltf::ParseUnsignedProperty(&result, &err, zero_obj, "zero", true));
|
||||
REQUIRE(err == "");
|
||||
REQUIRE(result == 0);
|
||||
}
|
||||
|
||||
SECTION("invalid integers") {
|
||||
std::string err;
|
||||
size_t result = -1;
|
||||
|
||||
CHECK_FALSE(tinygltf::ParseUnsignedProperty(&result, &err, {{"int", -1234}},
|
||||
"int", true));
|
||||
REQUIRE_THAT(err, Catch::Contains("not a positive integer"));
|
||||
|
||||
err.clear();
|
||||
CHECK_FALSE(tinygltf::ParseUnsignedProperty(&result, &err, {{"int", 0.5}},
|
||||
"int", true));
|
||||
REQUIRE_THAT(err, Catch::Contains("not a positive integer"));
|
||||
|
||||
// Excessively large values and NaN aren't allowed either.
|
||||
err.clear();
|
||||
CHECK_FALSE(tinygltf::ParseUnsignedProperty(&result, &err, {{"int", 1e300}},
|
||||
"int", true));
|
||||
REQUIRE_THAT(err, Catch::Contains("not a positive integer"));
|
||||
|
||||
err.clear();
|
||||
CHECK_FALSE(tinygltf::ParseUnsignedProperty(
|
||||
&result, &err, {{"int", std::numeric_limits<double>::quiet_NaN()}},
|
||||
"int", true));
|
||||
REQUIRE_THAT(err, Catch::Contains("not a positive integer"));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("parse-integer-array", "[bounds-checking]") {
|
||||
SECTION("parses valid integers") {
|
||||
std::string err;
|
||||
std::vector<int> result;
|
||||
CHECK(tinygltf::ParseIntegerArrayProperty(&result, &err,
|
||||
{{"x", {-1, 2, 3}}}, "x", true));
|
||||
REQUIRE(err == "");
|
||||
REQUIRE(result.size() == 3);
|
||||
REQUIRE(result[0] == -1);
|
||||
REQUIRE(result[1] == 2);
|
||||
REQUIRE(result[2] == 3);
|
||||
}
|
||||
|
||||
SECTION("invalid integers") {
|
||||
std::string err;
|
||||
std::vector<int> result;
|
||||
CHECK_FALSE(tinygltf::ParseIntegerArrayProperty(
|
||||
&result, &err, {{"x", {-1, 1e300, 3}}}, "x", true));
|
||||
REQUIRE_THAT(err, Catch::Contains("not an integer type"));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("pbr-khr-texture-transform", "[material]") {
|
||||
tinygltf::Model model;
|
||||
tinygltf::TinyGLTF ctx;
|
||||
std::string err;
|
||||
std::string warn;
|
||||
|
||||
// Loading is expected to fail, but not crash.
|
||||
bool ret = ctx.LoadASCIIFromFile(
|
||||
&model, &err, &warn,
|
||||
"../models/Cube-texture-ext/Cube-textransform.gltf");
|
||||
REQUIRE(ret == true);
|
||||
|
||||
REQUIRE(model.materials.size() == 2);
|
||||
REQUIRE(model.materials[0].emissiveTexture.extensions.count("KHR_texture_transform") == 1);
|
||||
REQUIRE(model.materials[0].emissiveTexture.extensions["KHR_texture_transform"].IsObject());
|
||||
|
||||
tinygltf::Value::Object &texform = model.materials[0].emissiveTexture.extensions["KHR_texture_transform"].Get<tinygltf::Value::Object>();
|
||||
|
||||
REQUIRE(texform.count("scale"));
|
||||
|
||||
REQUIRE(texform["scale"].IsArray());
|
||||
|
||||
// Note: It looks json.hpp parse integer JSON number as integer, not floating point.
|
||||
// IsNumber return true either value is int or floating point.
|
||||
REQUIRE(texform["scale"].Get(0).IsNumber());
|
||||
REQUIRE(texform["scale"].Get(1).IsNumber());
|
||||
|
||||
double scale[2];
|
||||
scale[0] = texform["scale"].Get(0).GetNumberAsDouble();
|
||||
scale[1] = texform["scale"].Get(1).GetNumberAsDouble();
|
||||
|
||||
REQUIRE(scale[0] == Approx(1.0));
|
||||
REQUIRE(scale[1] == Approx(-1.0));
|
||||
|
||||
}
|
||||
|
||||
3055
tiny_gltf.h
3055
tiny_gltf.h
File diff suppressed because it is too large
Load Diff
20
tools/schema_ref_resolver/README.md
Normal file
20
tools/schema_ref_resolver/README.md
Normal file
@@ -0,0 +1,20 @@
|
||||
Create single schema string by deferencing $ref in glTF schema JSON for embedding JSON scheme string in C++.
|
||||
|
||||
## Setup
|
||||
|
||||
Use python2
|
||||
|
||||
```
|
||||
$ pip2 install jsonpath-rw
|
||||
$ pip2 install simplejson
|
||||
```
|
||||
|
||||
Put `ref_resolver.py` and `generate_single_schema_doc.py` to glTF schema direcotry(i.e. `$glTF/specification/2.0/schema`)
|
||||
|
||||
## Generate
|
||||
|
||||
Run `generate_single_schema_doc.py`
|
||||
|
||||
## TODO
|
||||
|
||||
* [ ] Print date and git commit id?
|
||||
37
tools/schema_ref_resolver/generate_single_schema_doc.py
Normal file
37
tools/schema_ref_resolver/generate_single_schema_doc.py
Normal file
@@ -0,0 +1,37 @@
|
||||
# for print with `end` parameter
|
||||
from __future__ import print_function
|
||||
|
||||
import json
|
||||
from ref_resolver import RefResolver
|
||||
import base64
|
||||
|
||||
f = open("glTF.schema.json")
|
||||
j = json.loads(f.read())
|
||||
|
||||
# call to API resolve method
|
||||
RefResolver("glTF.schema.json").resolve(j)
|
||||
|
||||
j_str = json.dumps(j, indent=2)
|
||||
|
||||
|
||||
# Run json.dumps twice to get escaped string
|
||||
escaped_str = json.dumps(j_str)
|
||||
|
||||
# MSVC does not accept string larger than 16K.
|
||||
# https://docs.microsoft.com/en-us/cpp/error-messages/compiler-errors-1/compiler-error-c2026?view=vs-2019
|
||||
# Also, it has a hard limit of 65,535 bytes even splitting a string with double quotation.
|
||||
# So, we write string as an array of string(then application must concatenate it)
|
||||
|
||||
# https://stackoverflow.com/questions/9475241/split-string-every-nth-character
|
||||
n = 8000 # Conservative number
|
||||
|
||||
splitted_string = [escaped_str[i:i+n] for i in range(0, len(escaped_str), n)]
|
||||
#print(len(splitted_string))
|
||||
|
||||
print("const char *kglTFSchemaStrings[] = {", end='')
|
||||
for (i, s) in enumerate(splitted_string):
|
||||
print(s, end='')
|
||||
if i != (len(splitted_string) - 1):
|
||||
print('",\n"', end='')
|
||||
|
||||
print("};")
|
||||
16
tools/schema_ref_resolver/glTF.schema.resolved.inc
Normal file
16
tools/schema_ref_resolver/glTF.schema.resolved.inc
Normal file
File diff suppressed because one or more lines are too long
2739
tools/schema_ref_resolver/glTF.schema.resolved.json
Normal file
2739
tools/schema_ref_resolver/glTF.schema.resolved.json
Normal file
File diff suppressed because it is too large
Load Diff
82
tools/schema_ref_resolver/ref_resolver.py
Normal file
82
tools/schema_ref_resolver/ref_resolver.py
Normal file
@@ -0,0 +1,82 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
from urlparse import urlparse, urljoin
|
||||
import simplejson as json
|
||||
from os.path import isfile
|
||||
import jsonpath_rw
|
||||
import requests
|
||||
|
||||
|
||||
cache = {}
|
||||
|
||||
class RefResolver:
|
||||
|
||||
def __init__(self):
|
||||
self.url_fragments = None
|
||||
|
||||
def __init__(self, id):
|
||||
self.id = id
|
||||
if id is not None:
|
||||
self.url_fragments = urlparse(id)
|
||||
else:
|
||||
self.url_fragments = None
|
||||
|
||||
|
||||
|
||||
def resolve(self, json_obj):
|
||||
if isinstance(json_obj, dict):
|
||||
for key, value in json_obj.items():
|
||||
if key == "$ref":
|
||||
ref_frag = urlparse(value)
|
||||
ref_file = ref_frag.netloc + ref_frag.path
|
||||
json_dump = {}
|
||||
if ref_file in cache:
|
||||
json_dump = cache[ref_file]
|
||||
else:
|
||||
if self.url_fragments.scheme in ['http', 'https']:
|
||||
ref_url = urljoin(self.id, ref_file)
|
||||
if callable(requests.Response.json):
|
||||
json_dump = requests.get(ref_url).json()
|
||||
else:
|
||||
json_dump = requests.get(ref_url).json
|
||||
ref_id = None
|
||||
if 'tilte' in json_dump:
|
||||
ref_id = json_dump['title']
|
||||
cache[ref_file] = json_dump
|
||||
RefResolver(ref_id).resolve(json_dump)
|
||||
cache[ref_file] = json_dump
|
||||
#elif self.url_fragments.scheme == 'file':
|
||||
else:
|
||||
if isfile(ref_file):
|
||||
# if the ref is another file -> go there and get it
|
||||
json_dump = json.load(open(ref_file))
|
||||
ref_id = None
|
||||
if 'title' in json_dump:
|
||||
ref_id = json_dump['title']
|
||||
cache[ref_file] = json_dump
|
||||
RefResolver(ref_id).resolve(json_dump)
|
||||
cache[ref_file] = json_dump
|
||||
else:
|
||||
# if the ref is in the same file grab it from the same file
|
||||
json_dump = json.load(open(self.url_fragments.netloc+self.url_fragments.path))
|
||||
cache[ref_file] = json_dump
|
||||
|
||||
ref_path_expr = "$" + ".".join(ref_frag.fragment.split("/"))
|
||||
path_expression = jsonpath_rw.parse(ref_path_expr)
|
||||
list_of_values = [match.value for match in path_expression.find(json_dump)]
|
||||
|
||||
if len(list_of_values) > 0:
|
||||
resolution = list_of_values[0]
|
||||
return resolution
|
||||
|
||||
resolved = self.resolve(value)
|
||||
if resolved is not None:
|
||||
json_obj[key] = resolved
|
||||
elif isinstance(json_obj, list):
|
||||
for (key, value) in enumerate(json_obj):
|
||||
resolved = self.resolve(value)
|
||||
if resolved is not None:
|
||||
json_obj[key] = resolved
|
||||
return None
|
||||
Reference in New Issue
Block a user