Implement GLB/glTF 3D model import
This commit is contained in:
@@ -5355,7 +5355,7 @@ msgstr ""
|
||||
msgid "Save current project as"
|
||||
msgstr ""
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr ""
|
||||
|
||||
msgid "Load a model"
|
||||
|
||||
@@ -5833,8 +5833,8 @@ msgstr "Desa el projecte com a"
|
||||
msgid "Save current project as"
|
||||
msgstr "Desar el projecte actual com"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Importar 3MF STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Importar 3MF STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Carregar un model"
|
||||
|
||||
@@ -5740,8 +5740,8 @@ msgstr "Uložit projekt jako"
|
||||
msgid "Save current project as"
|
||||
msgstr "Uložit aktuální projekt jako"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Importovat 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Importovat 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Načíst model"
|
||||
|
||||
@@ -5886,8 +5886,8 @@ msgstr "Projekt speichern als"
|
||||
msgid "Save current project as"
|
||||
msgstr "Aktuelles Projekt speichern als"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Importiere 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Importiere 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Lade ein Modell"
|
||||
|
||||
@@ -5412,7 +5412,7 @@ msgstr ""
|
||||
msgid "Save current project as"
|
||||
msgstr ""
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr ""
|
||||
|
||||
msgid "Load a model"
|
||||
|
||||
@@ -5873,8 +5873,8 @@ msgstr "Guardar proyecto como"
|
||||
msgid "Save current project as"
|
||||
msgstr "Guardar el proyecto actual como"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Importar 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Importar 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Cargar un modelo"
|
||||
|
||||
@@ -5883,8 +5883,8 @@ msgstr "Enregistrer le projet sous"
|
||||
msgid "Save current project as"
|
||||
msgstr "Enregistrer le projet actuel sous"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Importer des fichiers 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Importer des fichiers 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Charger un modèle"
|
||||
|
||||
@@ -5813,8 +5813,8 @@ msgstr "Projekt mentése másként"
|
||||
msgid "Save current project as"
|
||||
msgstr "Jelenlegi projekt mentése másként"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Importálás 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Importálás 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Modell betöltése"
|
||||
|
||||
@@ -5865,8 +5865,8 @@ msgstr "Salva Progetto come"
|
||||
msgid "Save current project as"
|
||||
msgstr "Salva Progetto corrente come"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Importa 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Importa 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Carica modello"
|
||||
|
||||
@@ -5691,8 +5691,8 @@ msgstr "プロジェクトを名前を付けて保存"
|
||||
msgid "Save current project as"
|
||||
msgstr "プロジェクトを名前を付けて保存"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "3MF/STL/STEP/SVG/OBJ/AMFをインポート"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMFをインポート"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "モデルを読み込む"
|
||||
|
||||
@@ -5647,8 +5647,8 @@ msgstr "프로젝트 다른 이름으로 저장"
|
||||
msgid "Save current project as"
|
||||
msgstr "현재 프로젝트 다른 이름으로 저장"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "3MF/STL/STEP/SVG/OBJ/AMF 가져오기"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF 가져오기"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "모델 불러오기"
|
||||
|
||||
@@ -5796,8 +5796,8 @@ msgstr "Išsaugoti projektą kaip"
|
||||
msgid "Save current project as"
|
||||
msgstr "Išsaugoti dabartinį projektą kaip"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Importuoti 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Importuoti 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Įkelti modelį"
|
||||
|
||||
@@ -5783,8 +5783,8 @@ msgstr "Bewaar project als"
|
||||
msgid "Save current project as"
|
||||
msgstr "Bewaar huidig project als"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "3MF/STL/STEP/SVG/OBJ/AMF importeren"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF importeren"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Laad een model"
|
||||
|
||||
@@ -5638,8 +5638,8 @@ msgstr "Zapisz projekt jako"
|
||||
msgid "Save current project as"
|
||||
msgstr "Zapisz bieżący projekt jako"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Importuj 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Importuj 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Wczytaj model"
|
||||
|
||||
@@ -5831,8 +5831,8 @@ msgstr "Salvar projeto como"
|
||||
msgid "Save current project as"
|
||||
msgstr "Salvar o projeto atual como"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Importar 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Importar 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Carregar um modelo"
|
||||
|
||||
@@ -5977,8 +5977,8 @@ msgstr "Сохранить проект как"
|
||||
msgid "Save current project as"
|
||||
msgstr "Сохранить текущий проект как"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Импорт 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Импорт 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Загрузка модели"
|
||||
|
||||
@@ -5752,8 +5752,8 @@ msgstr "Spara Projekt som"
|
||||
msgid "Save current project as"
|
||||
msgstr "Spara nuvarande projekt som"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Importera 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Importera 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Ladda modell"
|
||||
|
||||
@@ -5381,8 +5381,8 @@ msgstr "Projeyi farklı kaydet"
|
||||
msgid "Save current project as"
|
||||
msgstr "Mevcut projeyi farklı kaydet"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "3MF/STL/STEP/SVG/OBJ/AMF'yi içe aktar"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF'yi içe aktar"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Model yükle"
|
||||
|
||||
@@ -5638,8 +5638,8 @@ msgstr "Зберегти проєкт як"
|
||||
msgid "Save current project as"
|
||||
msgstr "Зберегти поточний проєкт як"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Імпорт 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Імпорт 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Завантажте модель"
|
||||
|
||||
@@ -5713,8 +5713,8 @@ msgstr "Lưu dự án thành"
|
||||
msgid "Save current project as"
|
||||
msgstr "Lưu dự án hiện tại thành"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Nhập 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Nhập 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Tải model"
|
||||
|
||||
@@ -5525,8 +5525,8 @@ msgstr "项目另存为"
|
||||
msgid "Save current project as"
|
||||
msgstr "项目另存为"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "导入 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "导入 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "加载模型"
|
||||
|
||||
@@ -5594,8 +5594,8 @@ msgstr "另存專案為"
|
||||
msgid "Save current project as"
|
||||
msgstr "將目前專案另存為"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "匯入 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "匯入 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "載入模型"
|
||||
|
||||
@@ -200,6 +200,10 @@ set(lisbslic3r_sources
|
||||
Format/DRC.hpp
|
||||
Format/bbs_3mf.cpp
|
||||
Format/bbs_3mf.hpp
|
||||
Format/GLTF.cpp
|
||||
Format/GLTF.hpp
|
||||
Format/ImportedTexture.cpp
|
||||
Format/ImportedTexture.hpp
|
||||
format.hpp
|
||||
Format/OBJ.cpp
|
||||
Format/OBJ.hpp
|
||||
@@ -623,6 +627,7 @@ target_link_libraries(libslic3r
|
||||
qhull
|
||||
qoi
|
||||
semver
|
||||
tinygltf_v3
|
||||
TBB::tbb
|
||||
TBB::tbbmalloc
|
||||
ZLIB::ZLIB
|
||||
|
||||
994
src/libslic3r/Format/GLTF.cpp
Normal file
994
src/libslic3r/Format/GLTF.cpp
Normal file
@@ -0,0 +1,994 @@
|
||||
// original author: sentientstardust
|
||||
|
||||
#include "GLTF.hpp"
|
||||
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
|
||||
#include <tiny_gltf_v3.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
|
||||
#include <Eigen/Geometry>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace {
|
||||
|
||||
static std::string percent_decode(const std::string &value);
|
||||
|
||||
static std::string tg3_string(const tg3_str &str)
|
||||
{
|
||||
return str.data == nullptr ? std::string() : std::string(str.data, str.len);
|
||||
}
|
||||
|
||||
static uint32_t rgba_to_packed(const RGBA &color)
|
||||
{
|
||||
const uint32_t r = uint32_t(std::lround(std::clamp(color[0], 0.f, 1.f) * 255.f)) & 0xFFu;
|
||||
const uint32_t g = uint32_t(std::lround(std::clamp(color[1], 0.f, 1.f) * 255.f)) & 0xFFu;
|
||||
const uint32_t b = uint32_t(std::lround(std::clamp(color[2], 0.f, 1.f) * 255.f)) & 0xFFu;
|
||||
const uint32_t a = uint32_t(std::lround(std::clamp(color[3], 0.f, 1.f) * 255.f)) & 0xFFu;
|
||||
return (r << 24) | (g << 16) | (b << 8) | a;
|
||||
}
|
||||
|
||||
static int32_t tg3_read_file(uint8_t **out_data, uint64_t *out_size, const char *path, uint32_t path_len, void *)
|
||||
{
|
||||
if (out_data == nullptr || out_size == nullptr || path == nullptr)
|
||||
return 0;
|
||||
|
||||
*out_data = nullptr;
|
||||
*out_size = 0;
|
||||
|
||||
const std::string filename = percent_decode(std::string(path, path_len));
|
||||
boost::nowide::ifstream ifs(filename, std::ios::binary | std::ios::ate);
|
||||
if (!ifs.is_open())
|
||||
return 0;
|
||||
|
||||
const std::streamoff stream_size = ifs.tellg();
|
||||
if (stream_size <= 0)
|
||||
return 0;
|
||||
ifs.seekg(0, std::ios::beg);
|
||||
|
||||
uint8_t *data = static_cast<uint8_t *>(std::malloc(size_t(stream_size)));
|
||||
if (data == nullptr)
|
||||
return 0;
|
||||
|
||||
if (!ifs.read(reinterpret_cast<char *>(data), stream_size)) {
|
||||
std::free(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
*out_data = data;
|
||||
*out_size = uint64_t(stream_size);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void tg3_free_file(uint8_t *data, uint64_t, void *)
|
||||
{
|
||||
std::free(data);
|
||||
}
|
||||
|
||||
static int attribute_index(const tg3_primitive &primitive, const char *name)
|
||||
{
|
||||
const size_t name_len = std::strlen(name);
|
||||
for (uint32_t i = 0; i < primitive.attributes_count; ++i) {
|
||||
const tg3_str &key = primitive.attributes[i].key;
|
||||
if (key.len == name_len && key.data != nullptr && std::memcmp(key.data, name, name_len) == 0)
|
||||
return primitive.attributes[i].value;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static size_t component_size(int32_t component_type)
|
||||
{
|
||||
switch (component_type) {
|
||||
case TG3_COMPONENT_TYPE_BYTE:
|
||||
case TG3_COMPONENT_TYPE_UNSIGNED_BYTE:
|
||||
return 1;
|
||||
case TG3_COMPONENT_TYPE_SHORT:
|
||||
case TG3_COMPONENT_TYPE_UNSIGNED_SHORT:
|
||||
return 2;
|
||||
case TG3_COMPONENT_TYPE_INT:
|
||||
case TG3_COMPONENT_TYPE_UNSIGNED_INT:
|
||||
case TG3_COMPONENT_TYPE_FLOAT:
|
||||
return 4;
|
||||
case TG3_COMPONENT_TYPE_DOUBLE:
|
||||
return 8;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static size_t accessor_component_count(int32_t type)
|
||||
{
|
||||
switch (type) {
|
||||
case TG3_TYPE_SCALAR:
|
||||
return 1;
|
||||
case TG3_TYPE_VEC2:
|
||||
return 2;
|
||||
case TG3_TYPE_VEC3:
|
||||
return 3;
|
||||
case TG3_TYPE_VEC4:
|
||||
return 4;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
struct AccessorView
|
||||
{
|
||||
const tg3_accessor *accessor{nullptr};
|
||||
const tg3_buffer_view *buffer_view{nullptr};
|
||||
const tg3_buffer *buffer{nullptr};
|
||||
size_t component_size{0};
|
||||
size_t component_count{0};
|
||||
size_t element_size{0};
|
||||
size_t stride{0};
|
||||
uint64_t base_offset{0};
|
||||
};
|
||||
|
||||
static bool make_accessor_view(const tg3_model &model, int accessor_idx, AccessorView &view)
|
||||
{
|
||||
view = {};
|
||||
if (accessor_idx < 0 || uint32_t(accessor_idx) >= model.accessors_count)
|
||||
return false;
|
||||
|
||||
const tg3_accessor &accessor = model.accessors[accessor_idx];
|
||||
if (accessor.buffer_view < 0 || uint32_t(accessor.buffer_view) >= model.buffer_views_count || accessor.sparse.is_sparse)
|
||||
return false;
|
||||
|
||||
const tg3_buffer_view &buffer_view = model.buffer_views[accessor.buffer_view];
|
||||
if (buffer_view.buffer < 0 || uint32_t(buffer_view.buffer) >= model.buffers_count)
|
||||
return false;
|
||||
|
||||
const tg3_buffer &buffer = model.buffers[buffer_view.buffer];
|
||||
const size_t comp_size = component_size(accessor.component_type);
|
||||
const size_t comp_count = accessor_component_count(accessor.type);
|
||||
if (comp_size == 0 || comp_count == 0)
|
||||
return false;
|
||||
|
||||
const size_t element_size = comp_size * comp_count;
|
||||
const size_t stride = buffer_view.byte_stride == 0 ? element_size : size_t(buffer_view.byte_stride);
|
||||
if (stride < element_size)
|
||||
return false;
|
||||
if (buffer_view.byte_offset > std::numeric_limits<uint64_t>::max() - accessor.byte_offset)
|
||||
return false;
|
||||
|
||||
const uint64_t base_offset = buffer_view.byte_offset + accessor.byte_offset;
|
||||
if (base_offset > buffer.data.count)
|
||||
return false;
|
||||
if (accessor.count > 0) {
|
||||
const uint64_t last_offset = base_offset + uint64_t(stride) * (accessor.count - 1);
|
||||
if (last_offset < base_offset || last_offset > buffer.data.count || uint64_t(element_size) > buffer.data.count - last_offset)
|
||||
return false;
|
||||
}
|
||||
|
||||
view.accessor = &accessor;
|
||||
view.buffer_view = &buffer_view;
|
||||
view.buffer = &buffer;
|
||||
view.component_size = comp_size;
|
||||
view.component_count = comp_count;
|
||||
view.element_size = element_size;
|
||||
view.stride = stride;
|
||||
view.base_offset = base_offset;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<class T> static T read_unaligned(const uint8_t *data)
|
||||
{
|
||||
T value;
|
||||
std::memcpy(&value, data, sizeof(T));
|
||||
return value;
|
||||
}
|
||||
|
||||
static double normalized_signed(double value, double max_value)
|
||||
{
|
||||
return std::max(value / max_value, -1.0);
|
||||
}
|
||||
|
||||
static bool read_component(const AccessorView &view, uint64_t element_idx, size_t component_idx, bool force_normalized, double &out)
|
||||
{
|
||||
if (view.accessor == nullptr || element_idx >= view.accessor->count || component_idx >= view.component_count)
|
||||
return false;
|
||||
|
||||
const uint8_t *ptr = view.buffer->data.data + view.base_offset + element_idx * view.stride + component_idx * view.component_size;
|
||||
const bool normalized = force_normalized || view.accessor->normalized != 0;
|
||||
|
||||
switch (view.accessor->component_type) {
|
||||
case TG3_COMPONENT_TYPE_BYTE: {
|
||||
const int8_t v = read_unaligned<int8_t>(ptr);
|
||||
out = normalized ? normalized_signed(double(v), 127.0) : double(v);
|
||||
return true;
|
||||
}
|
||||
case TG3_COMPONENT_TYPE_UNSIGNED_BYTE: {
|
||||
const uint8_t v = read_unaligned<uint8_t>(ptr);
|
||||
out = normalized ? double(v) / 255.0 : double(v);
|
||||
return true;
|
||||
}
|
||||
case TG3_COMPONENT_TYPE_SHORT: {
|
||||
const int16_t v = read_unaligned<int16_t>(ptr);
|
||||
out = normalized ? normalized_signed(double(v), 32767.0) : double(v);
|
||||
return true;
|
||||
}
|
||||
case TG3_COMPONENT_TYPE_UNSIGNED_SHORT: {
|
||||
const uint16_t v = read_unaligned<uint16_t>(ptr);
|
||||
out = normalized ? double(v) / 65535.0 : double(v);
|
||||
return true;
|
||||
}
|
||||
case TG3_COMPONENT_TYPE_INT: {
|
||||
const int32_t v = read_unaligned<int32_t>(ptr);
|
||||
out = normalized ? normalized_signed(double(v), 2147483647.0) : double(v);
|
||||
return true;
|
||||
}
|
||||
case TG3_COMPONENT_TYPE_UNSIGNED_INT: {
|
||||
const uint32_t v = read_unaligned<uint32_t>(ptr);
|
||||
out = normalized ? double(v) / 4294967295.0 : double(v);
|
||||
return true;
|
||||
}
|
||||
case TG3_COMPONENT_TYPE_FLOAT:
|
||||
out = double(read_unaligned<float>(ptr));
|
||||
return true;
|
||||
case TG3_COMPONENT_TYPE_DOUBLE:
|
||||
out = read_unaligned<double>(ptr);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool read_index(const AccessorView &view, uint64_t element_idx, uint32_t &out)
|
||||
{
|
||||
if (view.accessor == nullptr || view.accessor->type != TG3_TYPE_SCALAR || element_idx >= view.accessor->count)
|
||||
return false;
|
||||
|
||||
const uint8_t *ptr = view.buffer->data.data + view.base_offset + element_idx * view.stride;
|
||||
switch (view.accessor->component_type) {
|
||||
case TG3_COMPONENT_TYPE_UNSIGNED_BYTE:
|
||||
out = read_unaligned<uint8_t>(ptr);
|
||||
return true;
|
||||
case TG3_COMPONENT_TYPE_UNSIGNED_SHORT:
|
||||
out = read_unaligned<uint16_t>(ptr);
|
||||
return true;
|
||||
case TG3_COMPONENT_TYPE_UNSIGNED_INT:
|
||||
out = read_unaligned<uint32_t>(ptr);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool read_vec3(const AccessorView &view, uint64_t element_idx, Vec3f &out)
|
||||
{
|
||||
double x = 0.0;
|
||||
double y = 0.0;
|
||||
double z = 0.0;
|
||||
if (view.component_count < 3 ||
|
||||
!read_component(view, element_idx, 0, false, x) ||
|
||||
!read_component(view, element_idx, 1, false, y) ||
|
||||
!read_component(view, element_idx, 2, false, z))
|
||||
return false;
|
||||
out = Vec3f(float(x), float(y), float(z));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool read_vec2(const AccessorView &view, uint64_t element_idx, Vec2f &out)
|
||||
{
|
||||
double x = 0.0;
|
||||
double y = 0.0;
|
||||
if (view.component_count < 2 ||
|
||||
!read_component(view, element_idx, 0, false, x) ||
|
||||
!read_component(view, element_idx, 1, false, y))
|
||||
return false;
|
||||
out = Vec2f(float(x), float(y));
|
||||
return true;
|
||||
}
|
||||
|
||||
static RGBA read_color_or_white(const AccessorView *view, uint64_t element_idx)
|
||||
{
|
||||
if (view == nullptr)
|
||||
return {1.f, 1.f, 1.f, 1.f};
|
||||
|
||||
double r = 1.0;
|
||||
double g = 1.0;
|
||||
double b = 1.0;
|
||||
double a = 1.0;
|
||||
const bool force_normalized = view->accessor->component_type != TG3_COMPONENT_TYPE_FLOAT &&
|
||||
view->accessor->component_type != TG3_COMPONENT_TYPE_DOUBLE;
|
||||
if (view->component_count < 3 ||
|
||||
!read_component(*view, element_idx, 0, force_normalized, r) ||
|
||||
!read_component(*view, element_idx, 1, force_normalized, g) ||
|
||||
!read_component(*view, element_idx, 2, force_normalized, b))
|
||||
return {1.f, 1.f, 1.f, 1.f};
|
||||
|
||||
if (view->component_count >= 4)
|
||||
read_component(*view, element_idx, 3, force_normalized, a);
|
||||
|
||||
return {float(std::clamp(r, 0.0, 1.0)),
|
||||
float(std::clamp(g, 0.0, 1.0)),
|
||||
float(std::clamp(b, 0.0, 1.0)),
|
||||
float(std::clamp(a, 0.0, 1.0))};
|
||||
}
|
||||
|
||||
static bool buffer_view_bytes(const tg3_model &model, int buffer_view_idx, const uint8_t *&data, size_t &size)
|
||||
{
|
||||
data = nullptr;
|
||||
size = 0;
|
||||
if (buffer_view_idx < 0 || uint32_t(buffer_view_idx) >= model.buffer_views_count)
|
||||
return false;
|
||||
const tg3_buffer_view &buffer_view = model.buffer_views[buffer_view_idx];
|
||||
if (buffer_view.buffer < 0 || uint32_t(buffer_view.buffer) >= model.buffers_count)
|
||||
return false;
|
||||
const tg3_buffer &buffer = model.buffers[buffer_view.buffer];
|
||||
if (buffer_view.byte_offset > buffer.data.count || buffer_view.byte_length > buffer.data.count - buffer_view.byte_offset)
|
||||
return false;
|
||||
data = buffer.data.data + buffer_view.byte_offset;
|
||||
size = size_t(buffer_view.byte_length);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int hex_value(char c)
|
||||
{
|
||||
if (c >= '0' && c <= '9')
|
||||
return c - '0';
|
||||
if (c >= 'a' && c <= 'f')
|
||||
return 10 + c - 'a';
|
||||
if (c >= 'A' && c <= 'F')
|
||||
return 10 + c - 'A';
|
||||
return -1;
|
||||
}
|
||||
|
||||
static std::string percent_decode(const std::string &value)
|
||||
{
|
||||
std::string out;
|
||||
out.reserve(value.size());
|
||||
for (size_t i = 0; i < value.size(); ++i) {
|
||||
if (value[i] == '%' && i + 2 < value.size()) {
|
||||
const int hi = hex_value(value[i + 1]);
|
||||
const int lo = hex_value(value[i + 2]);
|
||||
if (hi >= 0 && lo >= 0) {
|
||||
out.push_back(char((hi << 4) | lo));
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
out.push_back(value[i]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static int base64_value(char c)
|
||||
{
|
||||
if (c >= 'A' && c <= 'Z')
|
||||
return c - 'A';
|
||||
if (c >= 'a' && c <= 'z')
|
||||
return 26 + c - 'a';
|
||||
if (c >= '0' && c <= '9')
|
||||
return 52 + c - '0';
|
||||
if (c == '+')
|
||||
return 62;
|
||||
if (c == '/')
|
||||
return 63;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static bool decode_base64(const std::string &input, std::vector<uint8_t> &out)
|
||||
{
|
||||
out.clear();
|
||||
uint32_t val = 0;
|
||||
int valb = -8;
|
||||
for (const char c : input) {
|
||||
if (std::isspace(static_cast<unsigned char>(c)))
|
||||
continue;
|
||||
if (c == '=')
|
||||
break;
|
||||
const int decoded = base64_value(c);
|
||||
if (decoded < 0)
|
||||
return false;
|
||||
val = (val << 6) + decoded;
|
||||
valb += 6;
|
||||
if (valb >= 0) {
|
||||
out.push_back(uint8_t((val >> valb) & 0xFF));
|
||||
valb -= 8;
|
||||
}
|
||||
}
|
||||
return !out.empty();
|
||||
}
|
||||
|
||||
static bool decode_data_uri(const std::string &uri, std::vector<uint8_t> &bytes, std::string &mime_type)
|
||||
{
|
||||
bytes.clear();
|
||||
mime_type.clear();
|
||||
|
||||
if (uri.rfind("data:", 0) != 0)
|
||||
return false;
|
||||
|
||||
const size_t comma = uri.find(',');
|
||||
if (comma == std::string::npos)
|
||||
return false;
|
||||
|
||||
const std::string meta = uri.substr(5, comma - 5);
|
||||
const std::string payload = uri.substr(comma + 1);
|
||||
const bool is_base64 = meta.find(";base64") != std::string::npos;
|
||||
const size_t semicolon = meta.find(';');
|
||||
mime_type = semicolon == std::string::npos ? meta : meta.substr(0, semicolon);
|
||||
|
||||
if (!is_base64)
|
||||
return false;
|
||||
|
||||
return decode_base64(payload, bytes);
|
||||
}
|
||||
|
||||
struct GltfTextureState
|
||||
{
|
||||
std::vector<int> image_to_imported;
|
||||
std::vector<int> texture_to_imported;
|
||||
};
|
||||
|
||||
static bool decode_gltf_image(const tg3_model &model,
|
||||
const boost::filesystem::path &base_dir,
|
||||
const tg3_image &image,
|
||||
ImportedTextureImage &out)
|
||||
{
|
||||
out = {};
|
||||
out.name = tg3_string(image.name);
|
||||
|
||||
if (image.buffer_view >= 0) {
|
||||
const uint8_t *data = nullptr;
|
||||
size_t data_size = 0;
|
||||
if (!buffer_view_bytes(model, image.buffer_view, data, data_size))
|
||||
return false;
|
||||
return decode_image_texture_rgba_from_memory(data,
|
||||
data_size,
|
||||
tg3_string(image.mime_type),
|
||||
out.rgba,
|
||||
out.width,
|
||||
out.height);
|
||||
}
|
||||
|
||||
const std::string uri = tg3_string(image.uri);
|
||||
if (uri.empty())
|
||||
return false;
|
||||
|
||||
if (uri.rfind("data:", 0) == 0) {
|
||||
std::vector<uint8_t> bytes;
|
||||
std::string mime_type;
|
||||
if (!decode_data_uri(uri, bytes, mime_type))
|
||||
return false;
|
||||
return decode_image_texture_rgba_from_memory(bytes.data(),
|
||||
bytes.size(),
|
||||
mime_type,
|
||||
out.rgba,
|
||||
out.width,
|
||||
out.height);
|
||||
}
|
||||
|
||||
if (uri.find(':') != std::string::npos && !boost::algorithm::istarts_with(uri, "file:"))
|
||||
return false;
|
||||
|
||||
std::string decoded_uri = percent_decode(uri);
|
||||
if (boost::algorithm::istarts_with(decoded_uri, "file://"))
|
||||
decoded_uri = decoded_uri.substr(7);
|
||||
else if (boost::algorithm::istarts_with(decoded_uri, "file:"))
|
||||
decoded_uri = decoded_uri.substr(5);
|
||||
|
||||
boost::filesystem::path image_path(decoded_uri);
|
||||
if (!image_path.is_absolute())
|
||||
image_path = base_dir / image_path;
|
||||
|
||||
out.name = image_path.string();
|
||||
return decode_image_texture_rgba_from_file(image_path.lexically_normal().string(), out.rgba, out.width, out.height);
|
||||
}
|
||||
|
||||
static int imported_texture_index_for_gltf_texture(const tg3_model &model,
|
||||
const boost::filesystem::path &base_dir,
|
||||
int texture_idx,
|
||||
GltfTextureState &texture_state,
|
||||
GltfImportInfo &info)
|
||||
{
|
||||
if (texture_idx < 0 || uint32_t(texture_idx) >= model.textures_count)
|
||||
return -1;
|
||||
|
||||
if (texture_state.texture_to_imported.empty())
|
||||
texture_state.texture_to_imported.assign(model.textures_count, -2);
|
||||
if (texture_state.image_to_imported.empty())
|
||||
texture_state.image_to_imported.assign(model.images_count, -2);
|
||||
|
||||
int &cached_texture = texture_state.texture_to_imported[texture_idx];
|
||||
if (cached_texture != -2)
|
||||
return cached_texture;
|
||||
|
||||
const tg3_texture &texture = model.textures[texture_idx];
|
||||
if (texture.source < 0 || uint32_t(texture.source) >= model.images_count) {
|
||||
cached_texture = -1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int &cached_image = texture_state.image_to_imported[texture.source];
|
||||
if (cached_image != -2) {
|
||||
cached_texture = cached_image;
|
||||
return cached_texture;
|
||||
}
|
||||
|
||||
ImportedTextureImage imported;
|
||||
if (!decode_gltf_image(model, base_dir, model.images[texture.source], imported)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "glTF material texture image failed to decode image_index=" << texture.source;
|
||||
cached_image = -1;
|
||||
cached_texture = -1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
cached_image = int(info.textures.size());
|
||||
info.textures.emplace_back(std::move(imported));
|
||||
cached_texture = cached_image;
|
||||
return cached_texture;
|
||||
}
|
||||
|
||||
static RGBA material_base_color(const tg3_material *material)
|
||||
{
|
||||
if (material == nullptr)
|
||||
return {1.f, 1.f, 1.f, 1.f};
|
||||
|
||||
return {
|
||||
float(std::clamp(material->pbr_metallic_roughness.base_color_factor[0], 0.0, 1.0)),
|
||||
float(std::clamp(material->pbr_metallic_roughness.base_color_factor[1], 0.0, 1.0)),
|
||||
float(std::clamp(material->pbr_metallic_roughness.base_color_factor[2], 0.0, 1.0)),
|
||||
float(std::clamp(material->pbr_metallic_roughness.base_color_factor[3], 0.0, 1.0))
|
||||
};
|
||||
}
|
||||
|
||||
static const tg3_value *find_object_value(const tg3_value &value, const char *key)
|
||||
{
|
||||
if (value.type != TG3_VALUE_OBJECT || key == nullptr)
|
||||
return nullptr;
|
||||
|
||||
const size_t key_len = std::strlen(key);
|
||||
for (uint32_t i = 0; i < value.object_count; ++i) {
|
||||
const tg3_kv_pair &pair = value.object_data[i];
|
||||
if (pair.key.data != nullptr && pair.key.len == key_len && std::memcmp(pair.key.data, key, key_len) == 0)
|
||||
return &pair.value;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static const tg3_value *find_extension_value(const tg3_extras_ext &ext, const char *name)
|
||||
{
|
||||
if (name == nullptr)
|
||||
return nullptr;
|
||||
|
||||
const size_t name_len = std::strlen(name);
|
||||
for (uint32_t i = 0; i < ext.extensions_count; ++i) {
|
||||
const tg3_extension &extension = ext.extensions[i];
|
||||
if (extension.name.data != nullptr && extension.name.len == name_len && std::memcmp(extension.name.data, name, name_len) == 0)
|
||||
return &extension.value;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static bool value_to_int(const tg3_value &value, int &out)
|
||||
{
|
||||
if (value.type != TG3_VALUE_INT)
|
||||
return false;
|
||||
if (value.int_val < std::numeric_limits<int>::min() || value.int_val > std::numeric_limits<int>::max())
|
||||
return false;
|
||||
out = int(value.int_val);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool texture_info_from_extension_value(const tg3_value *value, int &texture_idx, int &tex_coord)
|
||||
{
|
||||
if (value == nullptr || value->type != TG3_VALUE_OBJECT)
|
||||
return false;
|
||||
|
||||
const tg3_value *index_value = find_object_value(*value, "index");
|
||||
if (index_value == nullptr || !value_to_int(*index_value, texture_idx))
|
||||
return false;
|
||||
|
||||
tex_coord = 0;
|
||||
if (const tg3_value *tex_coord_value = find_object_value(*value, "texCoord"))
|
||||
value_to_int(*tex_coord_value, tex_coord);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool diffuse_factor_from_extension_value(const tg3_value *value, RGBA &out)
|
||||
{
|
||||
if (value == nullptr || value->type != TG3_VALUE_ARRAY || value->array_count < 3)
|
||||
return false;
|
||||
|
||||
std::array<float, 4> factor{1.f, 1.f, 1.f, 1.f};
|
||||
for (uint32_t i = 0; i < std::min<uint32_t>(value->array_count, 4); ++i) {
|
||||
const tg3_value &component = value->array_data[i];
|
||||
if (component.type == TG3_VALUE_REAL)
|
||||
factor[i] = float(std::clamp(component.real_val, 0.0, 1.0));
|
||||
else if (component.type == TG3_VALUE_INT)
|
||||
factor[i] = float(std::clamp(double(component.int_val), 0.0, 1.0));
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
out = factor;
|
||||
return true;
|
||||
}
|
||||
|
||||
struct MaterialTextureSource
|
||||
{
|
||||
RGBA color{1.f, 1.f, 1.f, 1.f};
|
||||
int texture_idx{-1};
|
||||
int tex_coord{0};
|
||||
};
|
||||
|
||||
static MaterialTextureSource material_texture_source(const tg3_material *material)
|
||||
{
|
||||
MaterialTextureSource source;
|
||||
if (material == nullptr)
|
||||
return source;
|
||||
|
||||
source.color = material_base_color(material);
|
||||
source.texture_idx = material->pbr_metallic_roughness.base_color_texture.index;
|
||||
source.tex_coord = material->pbr_metallic_roughness.base_color_texture.tex_coord;
|
||||
|
||||
const tg3_value *spec_gloss = find_extension_value(material->ext, "KHR_materials_pbrSpecularGlossiness");
|
||||
if (spec_gloss != nullptr) {
|
||||
RGBA diffuse_factor;
|
||||
if (diffuse_factor_from_extension_value(find_object_value(*spec_gloss, "diffuseFactor"), diffuse_factor))
|
||||
source.color = diffuse_factor;
|
||||
|
||||
int diffuse_texture_idx = -1;
|
||||
int diffuse_tex_coord = 0;
|
||||
if (source.texture_idx < 0 &&
|
||||
texture_info_from_extension_value(find_object_value(*spec_gloss, "diffuseTexture"), diffuse_texture_idx, diffuse_tex_coord)) {
|
||||
source.texture_idx = diffuse_texture_idx;
|
||||
source.tex_coord = diffuse_tex_coord;
|
||||
}
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
static Eigen::Matrix4d node_transform(const tg3_node &node)
|
||||
{
|
||||
if (node.has_matrix) {
|
||||
Eigen::Matrix4d matrix;
|
||||
for (int col = 0; col < 4; ++col)
|
||||
for (int row = 0; row < 4; ++row)
|
||||
matrix(row, col) = node.matrix[col * 4 + row];
|
||||
return matrix;
|
||||
}
|
||||
|
||||
Eigen::Affine3d transform = Eigen::Affine3d::Identity();
|
||||
transform.translate(Eigen::Vector3d(node.translation[0], node.translation[1], node.translation[2]));
|
||||
Eigen::Quaterniond rotation(node.rotation[3], node.rotation[0], node.rotation[1], node.rotation[2]);
|
||||
if (rotation.norm() > 0.0)
|
||||
transform.rotate(rotation.normalized());
|
||||
transform.scale(Eigen::Vector3d(node.scale[0], node.scale[1], node.scale[2]));
|
||||
return transform.matrix();
|
||||
}
|
||||
|
||||
static bool build_primitive_indices(const tg3_model &model, const tg3_primitive &primitive, const AccessorView &positions, std::vector<uint32_t> &indices)
|
||||
{
|
||||
indices.clear();
|
||||
if (primitive.indices >= 0) {
|
||||
AccessorView index_view;
|
||||
if (!make_accessor_view(model, primitive.indices, index_view))
|
||||
return false;
|
||||
indices.reserve(size_t(index_view.accessor->count));
|
||||
for (uint64_t i = 0; i < index_view.accessor->count; ++i) {
|
||||
uint32_t index = 0;
|
||||
if (!read_index(index_view, i, index) || index >= positions.accessor->count)
|
||||
return false;
|
||||
indices.emplace_back(index);
|
||||
}
|
||||
} else {
|
||||
if (positions.accessor->count > std::numeric_limits<uint32_t>::max())
|
||||
return false;
|
||||
indices.reserve(size_t(positions.accessor->count));
|
||||
for (uint64_t i = 0; i < positions.accessor->count; ++i)
|
||||
indices.emplace_back(uint32_t(i));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void triangle_source_indices(int mode, const std::vector<uint32_t> &indices, size_t triangle_idx, std::array<uint32_t, 3> &out)
|
||||
{
|
||||
if (mode == TG3_MODE_TRIANGLES || mode == -1) {
|
||||
out = {indices[triangle_idx * 3], indices[triangle_idx * 3 + 1], indices[triangle_idx * 3 + 2]};
|
||||
} else if (mode == TG3_MODE_TRIANGLE_STRIP) {
|
||||
if (triangle_idx % 2 == 0)
|
||||
out = {indices[triangle_idx], indices[triangle_idx + 1], indices[triangle_idx + 2]};
|
||||
else
|
||||
out = {indices[triangle_idx + 1], indices[triangle_idx], indices[triangle_idx + 2]};
|
||||
} else {
|
||||
out = {indices[0], indices[triangle_idx + 1], indices[triangle_idx + 2]};
|
||||
}
|
||||
}
|
||||
|
||||
static size_t triangle_count_for_mode(int mode, size_t index_count)
|
||||
{
|
||||
if (mode == TG3_MODE_TRIANGLES || mode == -1)
|
||||
return index_count / 3;
|
||||
if (mode == TG3_MODE_TRIANGLE_STRIP || mode == TG3_MODE_TRIANGLE_FAN)
|
||||
return index_count >= 3 ? index_count - 2 : 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void append_mesh_primitive(const tg3_model &model,
|
||||
const boost::filesystem::path &base_dir,
|
||||
const tg3_primitive &primitive,
|
||||
const Eigen::Matrix4d &transform,
|
||||
indexed_triangle_set &its,
|
||||
GltfTextureState &texture_state,
|
||||
GltfImportInfo &info)
|
||||
{
|
||||
const int mode = primitive.mode == -1 ? TG3_MODE_TRIANGLES : primitive.mode;
|
||||
if (mode != TG3_MODE_TRIANGLES && mode != TG3_MODE_TRIANGLE_STRIP && mode != TG3_MODE_TRIANGLE_FAN)
|
||||
return;
|
||||
|
||||
AccessorView positions;
|
||||
if (!make_accessor_view(model, attribute_index(primitive, "POSITION"), positions) ||
|
||||
positions.accessor->type != TG3_TYPE_VEC3)
|
||||
return;
|
||||
|
||||
std::vector<uint32_t> source_indices;
|
||||
if (!build_primitive_indices(model, primitive, positions, source_indices))
|
||||
return;
|
||||
|
||||
const tg3_material *material = nullptr;
|
||||
bool has_material = false;
|
||||
int imported_texture_idx = -1;
|
||||
int texture_coord = 0;
|
||||
if (primitive.material >= 0 && uint32_t(primitive.material) < model.materials_count) {
|
||||
material = &model.materials[primitive.material];
|
||||
has_material = true;
|
||||
}
|
||||
|
||||
const MaterialTextureSource texture_source = material_texture_source(material);
|
||||
if (material != nullptr) {
|
||||
texture_coord = std::max(texture_source.tex_coord, 0);
|
||||
imported_texture_idx = imported_texture_index_for_gltf_texture(model, base_dir, texture_source.texture_idx, texture_state, info);
|
||||
}
|
||||
|
||||
const RGBA face_color = texture_source.color;
|
||||
const int color_accessor_idx = attribute_index(primitive, "COLOR_0");
|
||||
AccessorView color_view_storage;
|
||||
AccessorView *color_view = nullptr;
|
||||
if (make_accessor_view(model, color_accessor_idx, color_view_storage) &&
|
||||
(color_view_storage.accessor->type == TG3_TYPE_VEC3 || color_view_storage.accessor->type == TG3_TYPE_VEC4)) {
|
||||
color_view = &color_view_storage;
|
||||
info.has_vertex_colors = true;
|
||||
}
|
||||
|
||||
std::string uv_attr = "TEXCOORD_" + std::to_string(texture_coord);
|
||||
const int uv_accessor_idx = attribute_index(primitive, uv_attr.c_str());
|
||||
AccessorView uv_view_storage;
|
||||
AccessorView *uv_view = nullptr;
|
||||
if (imported_texture_idx >= 0 &&
|
||||
make_accessor_view(model, uv_accessor_idx, uv_view_storage) &&
|
||||
uv_view_storage.accessor->type == TG3_TYPE_VEC2)
|
||||
uv_view = &uv_view_storage;
|
||||
|
||||
const size_t tri_count = triangle_count_for_mode(mode, source_indices.size());
|
||||
for (size_t tri_idx = 0; tri_idx < tri_count; ++tri_idx) {
|
||||
std::array<uint32_t, 3> src{};
|
||||
triangle_source_indices(mode, source_indices, tri_idx, src);
|
||||
if (src[0] == src[1] || src[0] == src[2] || src[1] == src[2])
|
||||
continue;
|
||||
|
||||
std::array<Vec3f, 3> tri_positions;
|
||||
std::array<RGBA, 3> tri_colors;
|
||||
std::array<Vec2f, 3> tri_uv{Vec2f(0.f, 0.f), Vec2f(0.f, 0.f), Vec2f(0.f, 0.f)};
|
||||
bool valid_triangle = true;
|
||||
bool valid_uv = uv_view != nullptr;
|
||||
for (int corner = 0; corner < 3; ++corner) {
|
||||
Vec3f position;
|
||||
if (!read_vec3(positions, src[corner], position)) {
|
||||
valid_triangle = false;
|
||||
break;
|
||||
}
|
||||
|
||||
const Eigen::Vector4d transformed = transform * Eigen::Vector4d(position.x(), position.y(), position.z(), 1.0);
|
||||
tri_positions[corner] = Vec3f(float(transformed.x()), float(transformed.y()), float(transformed.z()));
|
||||
tri_colors[corner] = read_color_or_white(color_view, src[corner]);
|
||||
|
||||
if (uv_view != nullptr) {
|
||||
if (read_vec2(*uv_view, src[corner], tri_uv[corner]))
|
||||
tri_uv[corner].y() = 1.f - tri_uv[corner].y();
|
||||
else
|
||||
valid_uv = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid_triangle)
|
||||
continue;
|
||||
|
||||
Vec3i32 face;
|
||||
for (int corner = 0; corner < 3; ++corner) {
|
||||
face[corner] = int(its.vertices.size());
|
||||
its.vertices.emplace_back(tri_positions[corner]);
|
||||
info.vertex_colors.emplace_back(tri_colors[corner]);
|
||||
info.vertex_colors_rgba.emplace_back(rgba_to_packed(tri_colors[corner]));
|
||||
}
|
||||
|
||||
its.indices.emplace_back(face);
|
||||
info.triangle_uvs.emplace_back(tri_uv);
|
||||
info.triangle_uvs_valid.emplace_back(valid_uv ? uint8_t(1) : uint8_t(0));
|
||||
info.triangle_texture_indices.emplace_back(valid_uv ? imported_texture_idx : -1);
|
||||
info.material_colors.emplace_back(face_color);
|
||||
info.has_material_colors = info.has_material_colors || has_material;
|
||||
}
|
||||
}
|
||||
|
||||
static void append_node_meshes(const tg3_model &model,
|
||||
const boost::filesystem::path &base_dir,
|
||||
int node_idx,
|
||||
const Eigen::Matrix4d &parent_transform,
|
||||
std::vector<uint8_t> &visited,
|
||||
indexed_triangle_set &its,
|
||||
GltfTextureState &texture_state,
|
||||
GltfImportInfo &info)
|
||||
{
|
||||
if (node_idx < 0 || uint32_t(node_idx) >= model.nodes_count)
|
||||
return;
|
||||
if (visited[node_idx] != 0)
|
||||
return;
|
||||
visited[node_idx] = 1;
|
||||
|
||||
const tg3_node &node = model.nodes[node_idx];
|
||||
const Eigen::Matrix4d transform = parent_transform * node_transform(node);
|
||||
if (node.mesh >= 0 && uint32_t(node.mesh) < model.meshes_count) {
|
||||
const tg3_mesh &mesh = model.meshes[node.mesh];
|
||||
for (uint32_t primitive_idx = 0; primitive_idx < mesh.primitives_count; ++primitive_idx)
|
||||
append_mesh_primitive(model, base_dir, mesh.primitives[primitive_idx], transform, its, texture_state, info);
|
||||
}
|
||||
|
||||
for (uint32_t child_idx = 0; child_idx < node.children_count; ++child_idx)
|
||||
append_node_meshes(model, base_dir, node.children[child_idx], transform, visited, its, texture_state, info);
|
||||
|
||||
visited[node_idx] = 0;
|
||||
}
|
||||
|
||||
static std::vector<int> scene_root_nodes(const tg3_model &model)
|
||||
{
|
||||
if (model.scenes_count > 0) {
|
||||
const int scene_idx = model.default_scene >= 0 && uint32_t(model.default_scene) < model.scenes_count ? model.default_scene : 0;
|
||||
const tg3_scene &scene = model.scenes[scene_idx];
|
||||
return std::vector<int>(scene.nodes, scene.nodes + scene.nodes_count);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> is_child(model.nodes_count, 0);
|
||||
for (uint32_t node_idx = 0; node_idx < model.nodes_count; ++node_idx) {
|
||||
const tg3_node &node = model.nodes[node_idx];
|
||||
for (uint32_t child_idx = 0; child_idx < node.children_count; ++child_idx)
|
||||
if (node.children[child_idx] >= 0 && uint32_t(node.children[child_idx]) < model.nodes_count)
|
||||
is_child[node.children[child_idx]] = 1;
|
||||
}
|
||||
|
||||
std::vector<int> roots;
|
||||
for (uint32_t node_idx = 0; node_idx < model.nodes_count; ++node_idx)
|
||||
if (is_child[node_idx] == 0)
|
||||
roots.emplace_back(int(node_idx));
|
||||
return roots;
|
||||
}
|
||||
|
||||
static std::string first_error_message(const tg3_error_stack &errors)
|
||||
{
|
||||
const uint32_t count = tg3_errors_count(&errors);
|
||||
if (count == 0)
|
||||
return {};
|
||||
|
||||
const tg3_error_entry *entry = tg3_errors_get(&errors, 0);
|
||||
if (entry == nullptr || entry->message == nullptr)
|
||||
return {};
|
||||
|
||||
return entry->message;
|
||||
}
|
||||
|
||||
static void finish_import_info(GltfImportInfo &info)
|
||||
{
|
||||
if (!info.has_vertex_colors) {
|
||||
info.vertex_colors.clear();
|
||||
info.vertex_colors_rgba.clear();
|
||||
}
|
||||
|
||||
if (!info.has_material_colors) {
|
||||
info.material_colors.clear();
|
||||
info.is_single_material_color = false;
|
||||
return;
|
||||
}
|
||||
|
||||
info.is_single_material_color = !info.material_colors.empty();
|
||||
for (size_t i = 1; i < info.material_colors.size(); ++i) {
|
||||
if (!color_is_equal(info.material_colors.front(), info.material_colors[i])) {
|
||||
info.is_single_material_color = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool load_gltf(const char *path, Model *model, GltfImportInfo &import_info, std::string &message)
|
||||
{
|
||||
import_info = {};
|
||||
message.clear();
|
||||
if (path == nullptr || model == nullptr)
|
||||
return false;
|
||||
|
||||
tg3_model parsed_model{};
|
||||
tg3_error_stack errors{};
|
||||
tg3_error_stack_init(&errors);
|
||||
|
||||
tg3_parse_options options{};
|
||||
tg3_parse_options_init(&options);
|
||||
options.images_as_is = 1;
|
||||
options.validate_indices = 1;
|
||||
options.max_external_file_size = 1024ull * 1024ull * 1024ull;
|
||||
options.fs.read_file = tg3_read_file;
|
||||
options.fs.free_file = tg3_free_file;
|
||||
|
||||
const std::string input_path(path);
|
||||
const tg3_error_code parse_result = tg3_parse_file(&parsed_model,
|
||||
&errors,
|
||||
path,
|
||||
uint32_t(input_path.size()),
|
||||
&options);
|
||||
if (parse_result != TG3_OK) {
|
||||
message = first_error_message(errors);
|
||||
if (message.empty())
|
||||
message = "load_gltf: failed to parse";
|
||||
tg3_model_free(&parsed_model);
|
||||
tg3_error_stack_free(&errors);
|
||||
return false;
|
||||
}
|
||||
|
||||
indexed_triangle_set its;
|
||||
GltfTextureState texture_state;
|
||||
const boost::filesystem::path input_fs_path(input_path);
|
||||
const boost::filesystem::path base_dir = input_fs_path.parent_path();
|
||||
const std::vector<int> roots = scene_root_nodes(parsed_model);
|
||||
std::vector<uint8_t> visited(parsed_model.nodes_count, 0);
|
||||
for (int node_idx : roots)
|
||||
append_node_meshes(parsed_model,
|
||||
base_dir,
|
||||
node_idx,
|
||||
Eigen::Matrix4d::Identity(),
|
||||
visited,
|
||||
its,
|
||||
texture_state,
|
||||
import_info);
|
||||
|
||||
if (its.indices.empty() || its.vertices.empty()) {
|
||||
message = "load_gltf: file contains no supported mesh triangles";
|
||||
tg3_model_free(&parsed_model);
|
||||
tg3_error_stack_free(&errors);
|
||||
return false;
|
||||
}
|
||||
|
||||
TriangleMesh mesh(std::move(its));
|
||||
if (mesh.volume() < 0.0) {
|
||||
mesh.flip_triangles();
|
||||
for (std::array<Vec2f, 3> &uvs : import_info.triangle_uvs)
|
||||
std::swap(uvs[1], uvs[2]);
|
||||
}
|
||||
|
||||
const std::string object_name = input_fs_path.stem().empty() ? input_fs_path.filename().string() : input_fs_path.stem().string();
|
||||
model->add_object(object_name.c_str(), path, std::move(mesh));
|
||||
|
||||
finish_import_info(import_info);
|
||||
|
||||
tg3_model_free(&parsed_model);
|
||||
tg3_error_stack_free(&errors);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
33
src/libslic3r/Format/GLTF.hpp
Normal file
33
src/libslic3r/Format/GLTF.hpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#ifndef slic3r_Format_GLTF_hpp_
|
||||
#define slic3r_Format_GLTF_hpp_
|
||||
|
||||
#include "ImportedTexture.hpp"
|
||||
#include "OBJ.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Model;
|
||||
|
||||
struct GltfImportInfo
|
||||
{
|
||||
std::vector<RGBA> vertex_colors;
|
||||
std::vector<uint32_t> vertex_colors_rgba;
|
||||
bool has_vertex_colors{false};
|
||||
std::vector<RGBA> material_colors;
|
||||
bool has_material_colors{false};
|
||||
bool is_single_material_color{false};
|
||||
std::vector<std::array<Vec2f, 3>> triangle_uvs;
|
||||
std::vector<uint8_t> triangle_uvs_valid;
|
||||
std::vector<int> triangle_texture_indices;
|
||||
std::vector<ImportedTextureImage> textures;
|
||||
};
|
||||
|
||||
bool load_gltf(const char *path, Model *model, GltfImportInfo &import_info, std::string &message);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif
|
||||
365
src/libslic3r/Format/ImportedTexture.cpp
Normal file
365
src/libslic3r/Format/ImportedTexture.cpp
Normal file
@@ -0,0 +1,365 @@
|
||||
#include "ImportedTexture.hpp"
|
||||
|
||||
#include "libslic3r/PNGReadWrite.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <csetjmp>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
|
||||
#include <jpeglib.h>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
bool checked_rgba_buffer_size(size_t width, size_t height, size_t &buffer_size)
|
||||
{
|
||||
buffer_size = 0;
|
||||
if (width == 0 || height == 0)
|
||||
return false;
|
||||
if (width > std::numeric_limits<size_t>::max() / height)
|
||||
return false;
|
||||
const size_t pixel_count = width * height;
|
||||
if (pixel_count > std::numeric_limits<size_t>::max() / 4)
|
||||
return false;
|
||||
buffer_size = pixel_count * 4;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool decode_png_texture_rgba_from_memory(const uint8_t *data,
|
||||
size_t data_size,
|
||||
std::vector<uint8_t> &out_rgba,
|
||||
uint32_t &out_width,
|
||||
uint32_t &out_height)
|
||||
{
|
||||
out_rgba.clear();
|
||||
out_width = 0;
|
||||
out_height = 0;
|
||||
|
||||
if (data == nullptr || data_size == 0)
|
||||
return false;
|
||||
|
||||
png::ReadBuf rb{reinterpret_cast<const char *>(data), data_size};
|
||||
png::ImageColorscale img;
|
||||
if (!png::decode_colored_png(rb, img))
|
||||
return false;
|
||||
|
||||
if (img.cols == 0 || img.rows == 0 || (img.bytes_per_pixel != 3 && img.bytes_per_pixel != 4))
|
||||
return false;
|
||||
|
||||
size_t rgba_size = 0;
|
||||
if (!checked_rgba_buffer_size(img.cols, img.rows, rgba_size))
|
||||
return false;
|
||||
|
||||
const size_t row_stride = img.cols * size_t(img.bytes_per_pixel);
|
||||
if (img.buf.size() < img.rows * row_stride)
|
||||
return false;
|
||||
|
||||
out_rgba.assign(rgba_size, 255);
|
||||
for (size_t y = 0; y < img.rows; ++y) {
|
||||
const size_t src_row_off = y * row_stride;
|
||||
const size_t dst_row_off = y * img.cols * 4;
|
||||
for (size_t x = 0; x < img.cols; ++x) {
|
||||
const size_t src = src_row_off + x * size_t(img.bytes_per_pixel);
|
||||
const size_t dst = dst_row_off + x * 4;
|
||||
out_rgba[dst + 0] = img.buf[src + 0];
|
||||
out_rgba[dst + 1] = img.buf[src + 1];
|
||||
out_rgba[dst + 2] = img.buf[src + 2];
|
||||
out_rgba[dst + 3] = (img.bytes_per_pixel == 4) ? img.buf[src + 3] : uint8_t(255);
|
||||
}
|
||||
}
|
||||
|
||||
out_width = uint32_t(img.cols);
|
||||
out_height = uint32_t(img.rows);
|
||||
return true;
|
||||
}
|
||||
|
||||
struct JpegDecodeErrorManager
|
||||
{
|
||||
jpeg_error_mgr pub;
|
||||
jmp_buf setjmp_buffer;
|
||||
};
|
||||
|
||||
static void jpeg_decode_error_exit(j_common_ptr cinfo)
|
||||
{
|
||||
auto *err = reinterpret_cast<JpegDecodeErrorManager *>(cinfo->err);
|
||||
longjmp(err->setjmp_buffer, 1);
|
||||
}
|
||||
|
||||
static bool decode_jpeg_texture_rgba_from_memory(const uint8_t *data,
|
||||
size_t data_size,
|
||||
std::vector<uint8_t> &out_rgba,
|
||||
uint32_t &out_width,
|
||||
uint32_t &out_height)
|
||||
{
|
||||
out_rgba.clear();
|
||||
out_width = 0;
|
||||
out_height = 0;
|
||||
|
||||
if (data == nullptr || data_size == 0)
|
||||
return false;
|
||||
|
||||
jpeg_decompress_struct cinfo{};
|
||||
JpegDecodeErrorManager jerr{};
|
||||
cinfo.err = jpeg_std_error(&jerr.pub);
|
||||
jerr.pub.error_exit = jpeg_decode_error_exit;
|
||||
bool jpeg_created = false;
|
||||
auto destroy_jpeg = [&cinfo, &jpeg_created]() {
|
||||
if (jpeg_created) {
|
||||
jpeg_destroy_decompress(&cinfo);
|
||||
jpeg_created = false;
|
||||
}
|
||||
};
|
||||
|
||||
if (setjmp(jerr.setjmp_buffer)) {
|
||||
destroy_jpeg();
|
||||
return false;
|
||||
}
|
||||
|
||||
jpeg_create_decompress(&cinfo);
|
||||
jpeg_created = true;
|
||||
jpeg_mem_src(&cinfo, data, static_cast<unsigned long>(data_size));
|
||||
|
||||
if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) {
|
||||
destroy_jpeg();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!jpeg_start_decompress(&cinfo)) {
|
||||
destroy_jpeg();
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t width = cinfo.output_width;
|
||||
const uint32_t height = cinfo.output_height;
|
||||
const int components = cinfo.output_components;
|
||||
size_t rgba_size = 0;
|
||||
const size_t scanline_stride = size_t(width) * size_t(std::max(components, 0));
|
||||
if (!checked_rgba_buffer_size(width, height, rgba_size) ||
|
||||
components <= 0 ||
|
||||
scanline_stride > std::numeric_limits<JDIMENSION>::max()) {
|
||||
jpeg_finish_decompress(&cinfo);
|
||||
destroy_jpeg();
|
||||
return false;
|
||||
}
|
||||
|
||||
out_rgba.assign(rgba_size, uint8_t(255));
|
||||
JSAMPARRAY scanline = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo,
|
||||
JPOOL_IMAGE,
|
||||
JDIMENSION(scanline_stride),
|
||||
1);
|
||||
|
||||
uint32_t y = 0;
|
||||
while (cinfo.output_scanline < cinfo.output_height) {
|
||||
jpeg_read_scanlines(&cinfo, scanline, 1);
|
||||
const unsigned char *src = scanline[0];
|
||||
const uint32_t dst_y = height - 1 - y;
|
||||
for (uint32_t x = 0; x < width; ++x) {
|
||||
const size_t dst = (size_t(dst_y) * size_t(width) + size_t(x)) * 4;
|
||||
if (components >= 3) {
|
||||
const size_t s = size_t(x) * size_t(components);
|
||||
out_rgba[dst + 0] = src[s + 0];
|
||||
out_rgba[dst + 1] = src[s + 1];
|
||||
out_rgba[dst + 2] = src[s + 2];
|
||||
} else {
|
||||
const unsigned char g = src[x];
|
||||
out_rgba[dst + 0] = g;
|
||||
out_rgba[dst + 1] = g;
|
||||
out_rgba[dst + 2] = g;
|
||||
}
|
||||
out_rgba[dst + 3] = 255;
|
||||
}
|
||||
++y;
|
||||
}
|
||||
|
||||
if (!jpeg_finish_decompress(&cinfo)) {
|
||||
destroy_jpeg();
|
||||
return false;
|
||||
}
|
||||
destroy_jpeg();
|
||||
|
||||
out_width = width;
|
||||
out_height = height;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool has_png_signature(const uint8_t *data, size_t data_size)
|
||||
{
|
||||
static constexpr uint8_t signature[] = {0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n'};
|
||||
return data_size >= sizeof(signature) && std::equal(std::begin(signature), std::end(signature), data);
|
||||
}
|
||||
|
||||
static bool has_jpeg_signature(const uint8_t *data, size_t data_size)
|
||||
{
|
||||
return data_size >= 3 && data[0] == 0xff && data[1] == 0xd8 && data[2] == 0xff;
|
||||
}
|
||||
|
||||
bool decode_image_texture_rgba_from_file(const std::string &texture_path,
|
||||
std::vector<uint8_t> &out_rgba,
|
||||
uint32_t &out_width,
|
||||
uint32_t &out_height)
|
||||
{
|
||||
out_rgba.clear();
|
||||
out_width = 0;
|
||||
out_height = 0;
|
||||
|
||||
if (!is_supported_image_texture_path(texture_path))
|
||||
return false;
|
||||
|
||||
boost::nowide::ifstream ifs(texture_path, std::ios::binary);
|
||||
if (!ifs.is_open())
|
||||
return false;
|
||||
|
||||
std::string encoded_data((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
|
||||
if (encoded_data.empty())
|
||||
return false;
|
||||
|
||||
return decode_image_texture_rgba_from_memory(reinterpret_cast<const uint8_t *>(encoded_data.data()),
|
||||
encoded_data.size(),
|
||||
texture_path,
|
||||
out_rgba,
|
||||
out_width,
|
||||
out_height);
|
||||
}
|
||||
|
||||
bool decode_image_texture_rgba_from_memory(const uint8_t *data,
|
||||
size_t data_size,
|
||||
const std::string &mime_type_or_name,
|
||||
std::vector<uint8_t> &out_rgba,
|
||||
uint32_t &out_width,
|
||||
uint32_t &out_height)
|
||||
{
|
||||
out_rgba.clear();
|
||||
out_width = 0;
|
||||
out_height = 0;
|
||||
|
||||
if (data == nullptr || data_size == 0)
|
||||
return false;
|
||||
|
||||
if (boost::algorithm::iequals(mime_type_or_name, "image/png") ||
|
||||
boost::algorithm::iends_with(mime_type_or_name, ".png") ||
|
||||
has_png_signature(data, data_size))
|
||||
return decode_png_texture_rgba_from_memory(data, data_size, out_rgba, out_width, out_height);
|
||||
|
||||
if (boost::algorithm::iequals(mime_type_or_name, "image/jpeg") ||
|
||||
boost::algorithm::iequals(mime_type_or_name, "image/jpg") ||
|
||||
boost::algorithm::iends_with(mime_type_or_name, ".jpg") ||
|
||||
boost::algorithm::iends_with(mime_type_or_name, ".jpeg") ||
|
||||
has_jpeg_signature(data, data_size))
|
||||
return decode_jpeg_texture_rgba_from_memory(data, data_size, out_rgba, out_width, out_height);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool is_supported_image_texture_path(const std::string &texture_path)
|
||||
{
|
||||
return boost::algorithm::iends_with(texture_path, ".png") ||
|
||||
boost::algorithm::iends_with(texture_path, ".jpg") ||
|
||||
boost::algorithm::iends_with(texture_path, ".jpeg");
|
||||
}
|
||||
|
||||
struct ImportedTextureAtlasEntry
|
||||
{
|
||||
uint32_t x_offset{0};
|
||||
uint32_t width{0};
|
||||
uint32_t height{0};
|
||||
};
|
||||
|
||||
bool build_imported_texture_atlas(const std::vector<ImportedTextureImage> &textures,
|
||||
const std::vector<int> &triangle_texture_indices,
|
||||
std::vector<std::array<Vec2f, 3>> &triangle_uvs,
|
||||
std::vector<uint8_t> &triangle_uv_valid,
|
||||
std::vector<uint8_t> &atlas_rgba,
|
||||
uint32_t &atlas_width,
|
||||
uint32_t &atlas_height)
|
||||
{
|
||||
atlas_rgba.clear();
|
||||
atlas_width = 0;
|
||||
atlas_height = 0;
|
||||
|
||||
if (textures.empty() || triangle_uvs.size() != triangle_uv_valid.size() || triangle_uvs.size() != triangle_texture_indices.size())
|
||||
return false;
|
||||
|
||||
std::vector<ImportedTextureAtlasEntry> placements(textures.size());
|
||||
for (size_t i = 0; i < textures.size(); ++i) {
|
||||
const ImportedTextureImage &texture = textures[i];
|
||||
if (texture.width == 0 || texture.height == 0 || texture.rgba.empty())
|
||||
return false;
|
||||
size_t texture_rgba_size = 0;
|
||||
if (!checked_rgba_buffer_size(texture.width, texture.height, texture_rgba_size) ||
|
||||
texture.rgba.size() < texture_rgba_size ||
|
||||
texture.width > std::numeric_limits<uint32_t>::max() - atlas_width)
|
||||
return false;
|
||||
|
||||
placements[i].x_offset = atlas_width;
|
||||
placements[i].width = texture.width;
|
||||
placements[i].height = texture.height;
|
||||
atlas_width += texture.width;
|
||||
atlas_height = std::max(atlas_height, texture.height);
|
||||
}
|
||||
|
||||
size_t atlas_rgba_size = 0;
|
||||
if (atlas_width == 0 || atlas_height == 0 || !checked_rgba_buffer_size(atlas_width, atlas_height, atlas_rgba_size))
|
||||
return false;
|
||||
|
||||
atlas_rgba.assign(atlas_rgba_size, uint8_t(0));
|
||||
for (size_t i = 0; i < textures.size(); ++i) {
|
||||
const ImportedTextureImage &texture = textures[i];
|
||||
const ImportedTextureAtlasEntry &entry = placements[i];
|
||||
for (uint32_t y = 0; y < texture.height; ++y) {
|
||||
const size_t src_off = size_t(y) * size_t(texture.width) * 4;
|
||||
const size_t dst_off = (size_t(y) * size_t(atlas_width) + size_t(entry.x_offset)) * 4;
|
||||
std::copy(texture.rgba.begin() + src_off,
|
||||
texture.rgba.begin() + src_off + size_t(texture.width) * 4,
|
||||
atlas_rgba.begin() + dst_off);
|
||||
}
|
||||
}
|
||||
|
||||
auto wrap_uv = [](float value) {
|
||||
if (!std::isfinite(value))
|
||||
return 0.f;
|
||||
|
||||
constexpr float k_uv_epsilon = 1e-6f;
|
||||
if (value >= -k_uv_epsilon && value <= 1.f + k_uv_epsilon)
|
||||
return std::clamp(value, 0.f, 1.f);
|
||||
|
||||
const float wrapped = value - std::floor(value);
|
||||
return wrapped < 0.f ? wrapped + 1.f : wrapped;
|
||||
};
|
||||
|
||||
auto remap_uv = [&wrap_uv, &atlas_width, &atlas_height](const Vec2f &uv, const ImportedTextureAtlasEntry &entry) {
|
||||
const float u = wrap_uv(uv.x());
|
||||
const float v = wrap_uv(uv.y());
|
||||
return Vec2f((float(entry.x_offset) + u * float(entry.width)) / float(atlas_width),
|
||||
v * float(entry.height) / float(atlas_height));
|
||||
};
|
||||
|
||||
bool has_any_textured_triangle = false;
|
||||
for (size_t tri_idx = 0; tri_idx < triangle_uvs.size(); ++tri_idx) {
|
||||
triangle_uv_valid[tri_idx] = 0;
|
||||
const int texture_idx = triangle_texture_indices[tri_idx];
|
||||
if (texture_idx < 0 || size_t(texture_idx) >= textures.size())
|
||||
continue;
|
||||
|
||||
const ImportedTextureAtlasEntry &entry = placements[size_t(texture_idx)];
|
||||
triangle_uvs[tri_idx][0] = remap_uv(triangle_uvs[tri_idx][0], entry);
|
||||
triangle_uvs[tri_idx][1] = remap_uv(triangle_uvs[tri_idx][1], entry);
|
||||
triangle_uvs[tri_idx][2] = remap_uv(triangle_uvs[tri_idx][2], entry);
|
||||
triangle_uv_valid[tri_idx] = 1;
|
||||
has_any_textured_triangle = true;
|
||||
}
|
||||
|
||||
if (!has_any_textured_triangle) {
|
||||
atlas_rgba.clear();
|
||||
atlas_width = 0;
|
||||
atlas_height = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
44
src/libslic3r/Format/ImportedTexture.hpp
Normal file
44
src/libslic3r/Format/ImportedTexture.hpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#ifndef slic3r_Format_ImportedTexture_hpp_
|
||||
#define slic3r_Format_ImportedTexture_hpp_
|
||||
|
||||
#include "libslic3r/Point.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
struct ImportedTextureImage
|
||||
{
|
||||
std::string name;
|
||||
std::vector<uint8_t> rgba;
|
||||
uint32_t width{0};
|
||||
uint32_t height{0};
|
||||
};
|
||||
|
||||
bool checked_rgba_buffer_size(size_t width, size_t height, size_t &buffer_size);
|
||||
bool decode_image_texture_rgba_from_file(const std::string &texture_path,
|
||||
std::vector<uint8_t> &out_rgba,
|
||||
uint32_t &out_width,
|
||||
uint32_t &out_height);
|
||||
bool decode_image_texture_rgba_from_memory(const uint8_t *data,
|
||||
size_t data_size,
|
||||
const std::string &mime_type_or_name,
|
||||
std::vector<uint8_t> &out_rgba,
|
||||
uint32_t &out_width,
|
||||
uint32_t &out_height);
|
||||
bool is_supported_image_texture_path(const std::string &texture_path);
|
||||
bool build_imported_texture_atlas(const std::vector<ImportedTextureImage> &textures,
|
||||
const std::vector<int> &triangle_texture_indices,
|
||||
std::vector<std::array<Vec2f, 3>> &triangle_uvs,
|
||||
std::vector<uint8_t> &triangle_uv_valid,
|
||||
std::vector<uint8_t> &atlas_rgba,
|
||||
uint32_t &atlas_width,
|
||||
uint32_t &atlas_height);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif
|
||||
@@ -11,12 +11,12 @@
|
||||
#include "TriangleMeshSlicer.hpp"
|
||||
#include "TriangleSelector.hpp"
|
||||
#include "MaterialType.hpp"
|
||||
#include "PNGReadWrite.hpp"
|
||||
|
||||
#include "Format/AMF.hpp"
|
||||
#include "Format/svg.hpp"
|
||||
#include "Format/bbs_3mf.hpp"
|
||||
#include "Format/DRC.hpp"
|
||||
#include "Format/GLTF.hpp"
|
||||
// BBS
|
||||
#include "FaceDetector.hpp"
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include <float.h>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <csetjmp>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <unordered_map>
|
||||
@@ -34,11 +33,8 @@
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include <boost/nowide/iostream.hpp>
|
||||
|
||||
#include <jpeglib.h>
|
||||
|
||||
#include "SVG.hpp"
|
||||
#include <Eigen/Dense>
|
||||
#include <functional>
|
||||
@@ -147,198 +143,6 @@ static std::vector<std::string> resolve_obj_texture_path_candidates(const std::s
|
||||
return candidates;
|
||||
}
|
||||
|
||||
static bool decode_png_texture_rgba(const std::string &texture_path,
|
||||
std::vector<uint8_t> &out_rgba,
|
||||
uint32_t &out_width,
|
||||
uint32_t &out_height)
|
||||
{
|
||||
out_rgba.clear();
|
||||
out_width = 0;
|
||||
out_height = 0;
|
||||
|
||||
boost::nowide::ifstream ifs(texture_path, std::ios::binary);
|
||||
if (!ifs.is_open())
|
||||
return false;
|
||||
|
||||
std::string encoded_data((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
|
||||
if (encoded_data.empty())
|
||||
return false;
|
||||
|
||||
png::ReadBuf rb{encoded_data.data(), encoded_data.size()};
|
||||
png::ImageColorscale img;
|
||||
if (!png::decode_colored_png(rb, img))
|
||||
return false;
|
||||
|
||||
if (img.cols == 0 || img.rows == 0 || (img.bytes_per_pixel != 3 && img.bytes_per_pixel != 4))
|
||||
return false;
|
||||
|
||||
size_t rgba_size = 0;
|
||||
if (!checked_rgba_buffer_size(img.cols, img.rows, rgba_size))
|
||||
return false;
|
||||
|
||||
const size_t row_stride = img.cols * size_t(img.bytes_per_pixel);
|
||||
if (img.buf.size() < img.rows * row_stride)
|
||||
return false;
|
||||
|
||||
out_rgba.assign(rgba_size, 255);
|
||||
for (size_t y = 0; y < img.rows; ++y) {
|
||||
const size_t src_row_off = y * row_stride;
|
||||
const size_t dst_row_off = y * img.cols * 4;
|
||||
for (size_t x = 0; x < img.cols; ++x) {
|
||||
const size_t src = src_row_off + x * size_t(img.bytes_per_pixel);
|
||||
const size_t dst = dst_row_off + x * 4;
|
||||
out_rgba[dst + 0] = img.buf[src + 0];
|
||||
out_rgba[dst + 1] = img.buf[src + 1];
|
||||
out_rgba[dst + 2] = img.buf[src + 2];
|
||||
out_rgba[dst + 3] = (img.bytes_per_pixel == 4) ? img.buf[src + 3] : uint8_t(255);
|
||||
}
|
||||
}
|
||||
|
||||
out_width = uint32_t(img.cols);
|
||||
out_height = uint32_t(img.rows);
|
||||
return true;
|
||||
}
|
||||
|
||||
struct JpegDecodeErrorManager
|
||||
{
|
||||
jpeg_error_mgr pub;
|
||||
jmp_buf setjmp_buffer;
|
||||
};
|
||||
|
||||
static void jpeg_decode_error_exit(j_common_ptr cinfo)
|
||||
{
|
||||
auto *err = reinterpret_cast<JpegDecodeErrorManager *>(cinfo->err);
|
||||
longjmp(err->setjmp_buffer, 1);
|
||||
}
|
||||
|
||||
static bool decode_jpeg_texture_rgba(const std::string &texture_path,
|
||||
std::vector<uint8_t> &out_rgba,
|
||||
uint32_t &out_width,
|
||||
uint32_t &out_height)
|
||||
{
|
||||
out_rgba.clear();
|
||||
out_width = 0;
|
||||
out_height = 0;
|
||||
|
||||
boost::nowide::ifstream ifs(texture_path, std::ios::binary);
|
||||
if (!ifs.is_open())
|
||||
return false;
|
||||
|
||||
std::string encoded_data((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
|
||||
if (encoded_data.empty())
|
||||
return false;
|
||||
|
||||
jpeg_decompress_struct cinfo{};
|
||||
JpegDecodeErrorManager jerr{};
|
||||
cinfo.err = jpeg_std_error(&jerr.pub);
|
||||
jerr.pub.error_exit = jpeg_decode_error_exit;
|
||||
bool jpeg_created = false;
|
||||
auto destroy_jpeg = [&cinfo, &jpeg_created]() {
|
||||
if (jpeg_created) {
|
||||
jpeg_destroy_decompress(&cinfo);
|
||||
jpeg_created = false;
|
||||
}
|
||||
};
|
||||
|
||||
if (setjmp(jerr.setjmp_buffer)) {
|
||||
destroy_jpeg();
|
||||
return false;
|
||||
}
|
||||
|
||||
jpeg_create_decompress(&cinfo);
|
||||
jpeg_created = true;
|
||||
jpeg_mem_src(&cinfo,
|
||||
reinterpret_cast<const unsigned char *>(encoded_data.data()),
|
||||
static_cast<unsigned long>(encoded_data.size()));
|
||||
|
||||
if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) {
|
||||
destroy_jpeg();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!jpeg_start_decompress(&cinfo)) {
|
||||
destroy_jpeg();
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t width = cinfo.output_width;
|
||||
const uint32_t height = cinfo.output_height;
|
||||
const int components = cinfo.output_components;
|
||||
size_t rgba_size = 0;
|
||||
const size_t scanline_stride = size_t(width) * size_t(std::max(components, 0));
|
||||
if (!checked_rgba_buffer_size(width, height, rgba_size) ||
|
||||
components <= 0 ||
|
||||
scanline_stride > std::numeric_limits<JDIMENSION>::max()) {
|
||||
jpeg_finish_decompress(&cinfo);
|
||||
destroy_jpeg();
|
||||
return false;
|
||||
}
|
||||
|
||||
out_rgba.assign(rgba_size, uint8_t(255));
|
||||
JSAMPARRAY scanline = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo,
|
||||
JPOOL_IMAGE,
|
||||
JDIMENSION(scanline_stride),
|
||||
1);
|
||||
|
||||
uint32_t y = 0;
|
||||
while (cinfo.output_scanline < cinfo.output_height) {
|
||||
jpeg_read_scanlines(&cinfo, scanline, 1);
|
||||
const unsigned char *src = scanline[0];
|
||||
const uint32_t dst_y = height - 1 - y;
|
||||
for (uint32_t x = 0; x < width; ++x) {
|
||||
const size_t dst = (size_t(dst_y) * size_t(width) + size_t(x)) * 4;
|
||||
if (components >= 3) {
|
||||
const size_t s = size_t(x) * size_t(components);
|
||||
out_rgba[dst + 0] = src[s + 0];
|
||||
out_rgba[dst + 1] = src[s + 1];
|
||||
out_rgba[dst + 2] = src[s + 2];
|
||||
} else {
|
||||
const unsigned char g = src[x];
|
||||
out_rgba[dst + 0] = g;
|
||||
out_rgba[dst + 1] = g;
|
||||
out_rgba[dst + 2] = g;
|
||||
}
|
||||
out_rgba[dst + 3] = 255;
|
||||
}
|
||||
++y;
|
||||
}
|
||||
|
||||
if (!jpeg_finish_decompress(&cinfo)) {
|
||||
destroy_jpeg();
|
||||
return false;
|
||||
}
|
||||
destroy_jpeg();
|
||||
|
||||
out_width = width;
|
||||
out_height = height;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool decode_image_texture_rgba(const std::string &texture_path,
|
||||
std::vector<uint8_t> &out_rgba,
|
||||
uint32_t &out_width,
|
||||
uint32_t &out_height)
|
||||
{
|
||||
out_rgba.clear();
|
||||
out_width = 0;
|
||||
out_height = 0;
|
||||
|
||||
if (boost::algorithm::iends_with(texture_path, ".png"))
|
||||
return decode_png_texture_rgba(texture_path, out_rgba, out_width, out_height);
|
||||
|
||||
if (boost::algorithm::iends_with(texture_path, ".jpg") || boost::algorithm::iends_with(texture_path, ".jpeg"))
|
||||
return decode_jpeg_texture_rgba(texture_path, out_rgba, out_width, out_height);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool is_supported_obj_texture_path(const std::string &texture_path)
|
||||
{
|
||||
return boost::algorithm::iends_with(texture_path, ".png") ||
|
||||
boost::algorithm::iends_with(texture_path, ".jpg") ||
|
||||
boost::algorithm::iends_with(texture_path, ".jpeg");
|
||||
}
|
||||
|
||||
struct ObjTextureImage
|
||||
{
|
||||
std::string resolved_path;
|
||||
@@ -381,7 +185,7 @@ static size_t count_resolved_obj_albedo_texture_references(const std::string &ob
|
||||
const std::string texture_ref = extract_obj_texture_reference(map_kd_raw);
|
||||
const auto candidates = resolve_obj_texture_path_candidates(obj_path, texture_ref);
|
||||
for (const std::string &candidate : candidates) {
|
||||
if (!is_supported_obj_texture_path(candidate) || !boost::filesystem::exists(candidate))
|
||||
if (!is_supported_image_texture_path(candidate) || !boost::filesystem::exists(candidate))
|
||||
continue;
|
||||
|
||||
const std::string normalized = boost::filesystem::path(candidate).lexically_normal().string();
|
||||
@@ -422,7 +226,7 @@ static ObjTextureImportData load_obj_albedo_textures(const std::string &obj_path
|
||||
|
||||
ObjTextureImage image;
|
||||
image.resolved_path = candidate;
|
||||
if (!decode_image_texture_rgba(candidate, image.rgba, image.width, image.height)) {
|
||||
if (!decode_image_texture_rgba_from_file(candidate, image.rgba, image.width, image.height)) {
|
||||
had_decode_failure = true;
|
||||
last_failed_path = candidate;
|
||||
continue;
|
||||
@@ -938,6 +742,133 @@ Model Model::read_from_file(const std::string&
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (boost::algorithm::iends_with(input_file, ".gltf") || boost::algorithm::iends_with(input_file, ".glb")) {
|
||||
GltfImportInfo gltf_info;
|
||||
result = load_gltf(input_file.c_str(), &model, gltf_info, message);
|
||||
if (result) {
|
||||
for (ModelObject *obj : model.objects)
|
||||
if (obj != nullptr)
|
||||
obj->rotate(Geometry::deg2rad(90.0), Axis::X);
|
||||
|
||||
if (!message.empty())
|
||||
BOOST_LOG_TRIVIAL(error) << message;
|
||||
|
||||
const bool has_valid_texture_uvs = std::any_of(gltf_info.triangle_uvs_valid.begin(), gltf_info.triangle_uvs_valid.end(), [](uint8_t uv_valid) {
|
||||
return uv_valid != 0;
|
||||
});
|
||||
const ObjImportCapabilities capabilities{
|
||||
gltf_info.has_vertex_colors,
|
||||
gltf_info.has_material_colors,
|
||||
gltf_info.is_single_material_color,
|
||||
gltf_info.textures.size(),
|
||||
has_valid_texture_uvs
|
||||
};
|
||||
const bool has_mode_selection = bool(objModeFn);
|
||||
ObjImportMode import_mode = ObjImportMode::UseDefault;
|
||||
const bool has_usable_uv_texture_data = capabilities.texture_count > 0 && capabilities.has_valid_texture_uvs;
|
||||
if (has_mode_selection) {
|
||||
import_mode = objModeFn(capabilities);
|
||||
if (import_mode == ObjImportMode::UseDefault)
|
||||
import_mode = has_usable_uv_texture_data ? ObjImportMode::ImportTextures : ObjImportMode::ImportPaintedRegions;
|
||||
}
|
||||
|
||||
if (!is_cb_cancel && model.objects.size() == 1) {
|
||||
ModelObject *obj = model.objects.front();
|
||||
if (obj != nullptr && obj->volumes.size() == 1 && obj->volumes.front() != nullptr) {
|
||||
ModelVolume *volume = obj->volumes.front();
|
||||
|
||||
volume->imported_vertex_colors_rgba.clear();
|
||||
volume->imported_texture_uvs_per_face.clear();
|
||||
volume->imported_texture_uv_valid.clear();
|
||||
volume->imported_texture_rgba.clear();
|
||||
volume->imported_texture_raw_filament_offsets.clear();
|
||||
volume->imported_texture_width = 0;
|
||||
volume->imported_texture_height = 0;
|
||||
volume->imported_texture_raw_channels = 0;
|
||||
volume->imported_texture_raw_metadata_json.clear();
|
||||
volume->uv_map_generator_version = 0;
|
||||
bool has_imported_usable_uv_texture_data = false;
|
||||
|
||||
const size_t triangle_count = volume->mesh().its.indices.size();
|
||||
if (triangle_count == gltf_info.triangle_uvs.size() &&
|
||||
triangle_count == gltf_info.triangle_uvs_valid.size() &&
|
||||
triangle_count == gltf_info.triangle_texture_indices.size()) {
|
||||
std::vector<std::array<Vec2f, 3>> triangle_uvs = gltf_info.triangle_uvs;
|
||||
std::vector<uint8_t> triangle_uv_valid = gltf_info.triangle_uvs_valid;
|
||||
|
||||
const bool import_textures = has_mode_selection ?
|
||||
(import_mode == ObjImportMode::ImportTextures) :
|
||||
true;
|
||||
|
||||
if (import_textures) {
|
||||
std::vector<uint8_t> atlas_rgba;
|
||||
uint32_t atlas_width = 0;
|
||||
uint32_t atlas_height = 0;
|
||||
if (build_imported_texture_atlas(gltf_info.textures,
|
||||
gltf_info.triangle_texture_indices,
|
||||
triangle_uvs,
|
||||
triangle_uv_valid,
|
||||
atlas_rgba,
|
||||
atlas_width,
|
||||
atlas_height)) {
|
||||
volume->imported_texture_uvs_per_face.reserve(triangle_count * 6);
|
||||
volume->imported_texture_uv_valid.reserve(triangle_count);
|
||||
bool has_any_valid_uv_face = false;
|
||||
for (size_t face_idx = 0; face_idx < triangle_count; ++face_idx) {
|
||||
const std::array<Vec2f, 3> &uv = triangle_uvs[face_idx];
|
||||
volume->imported_texture_uvs_per_face.emplace_back(uv[0].x());
|
||||
volume->imported_texture_uvs_per_face.emplace_back(uv[0].y());
|
||||
volume->imported_texture_uvs_per_face.emplace_back(uv[1].x());
|
||||
volume->imported_texture_uvs_per_face.emplace_back(uv[1].y());
|
||||
volume->imported_texture_uvs_per_face.emplace_back(uv[2].x());
|
||||
volume->imported_texture_uvs_per_face.emplace_back(uv[2].y());
|
||||
volume->imported_texture_uv_valid.emplace_back(triangle_uv_valid[face_idx]);
|
||||
has_any_valid_uv_face = has_any_valid_uv_face || (triangle_uv_valid[face_idx] != 0);
|
||||
}
|
||||
volume->imported_texture_width = atlas_width;
|
||||
volume->imported_texture_height = atlas_height;
|
||||
volume->imported_texture_rgba = std::move(atlas_rgba);
|
||||
volume->imported_texture_raw_filament_offsets.clear();
|
||||
volume->imported_texture_raw_channels = 0;
|
||||
volume->imported_texture_raw_metadata_json.clear();
|
||||
volume->uv_map_generator_version = 0;
|
||||
has_imported_usable_uv_texture_data = has_any_valid_uv_face;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bool import_vertex_colors = has_mode_selection ?
|
||||
(import_mode == ObjImportMode::ImportPaintedRegions ||
|
||||
(import_mode == ObjImportMode::ImportTextures && !has_imported_usable_uv_texture_data)) :
|
||||
true;
|
||||
if (import_vertex_colors &&
|
||||
gltf_info.has_vertex_colors &&
|
||||
volume->mesh().its.vertices.size() == gltf_info.vertex_colors_rgba.size())
|
||||
volume->imported_vertex_colors_rgba = gltf_info.vertex_colors_rgba;
|
||||
}
|
||||
}
|
||||
|
||||
const bool import_painted_regions = has_mode_selection ?
|
||||
(import_mode == ObjImportMode::ImportPaintedRegions) :
|
||||
true;
|
||||
|
||||
if (!is_cb_cancel && import_painted_regions && objFn) {
|
||||
ObjDialogInOut in_out;
|
||||
in_out.model = &model;
|
||||
if (gltf_info.has_vertex_colors && !gltf_info.vertex_colors.empty()) {
|
||||
in_out.input_colors = gltf_info.vertex_colors;
|
||||
in_out.is_single_color = false;
|
||||
in_out.deal_vertex_color = true;
|
||||
objFn(in_out);
|
||||
} else if (gltf_info.has_material_colors && !gltf_info.material_colors.empty()) {
|
||||
in_out.input_colors = gltf_info.material_colors;
|
||||
in_out.is_single_color = gltf_info.is_single_material_color;
|
||||
in_out.deal_vertex_color = false;
|
||||
objFn(in_out);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (boost::algorithm::iends_with(input_file, ".svg"))
|
||||
result = load_svg(input_file.c_str(), &model, message);
|
||||
//BBS: remove the old .amf.xml files
|
||||
|
||||
@@ -563,10 +563,10 @@ static const FileWildcards file_wildcards_by_type[FT_SIZE] = {
|
||||
/* FT_GCODE */ { L("G-code files"), { ".gcode"sv} },
|
||||
#ifdef __APPLE__
|
||||
/* FT_MODEL */
|
||||
{L("Supported files"), {".3mf"sv, ".stl"sv, ".oltp"sv, ".stp"sv, ".step"sv, ".svg"sv, ".amf"sv, ".obj"sv, ".usd"sv, ".usda"sv, ".usdc"sv, ".usdz"sv, ".abc"sv, ".ply"sv, ".drc"sv}},
|
||||
{L("Supported files"), {".3mf"sv, ".stl"sv, ".oltp"sv, ".stp"sv, ".step"sv, ".svg"sv, ".amf"sv, ".obj"sv, ".gltf"sv, ".glb"sv, ".usd"sv, ".usda"sv, ".usdc"sv, ".usdz"sv, ".abc"sv, ".ply"sv, ".drc"sv}},
|
||||
#else
|
||||
/* FT_MODEL */
|
||||
{L("Supported files"), {".3mf"sv, ".stl"sv, ".oltp"sv, ".stp"sv, ".step"sv, ".svg"sv, ".amf"sv, ".obj"sv, ".drc"sv}},
|
||||
{L("Supported files"), {".3mf"sv, ".stl"sv, ".oltp"sv, ".stp"sv, ".step"sv, ".svg"sv, ".amf"sv, ".obj"sv, ".gltf"sv, ".glb"sv, ".drc"sv}},
|
||||
#endif
|
||||
/* FT_ZIP */ { L("ZIP files"), { ".zip"sv } },
|
||||
/* FT_PROJECT */ { L("Project files"), { ".3mf"sv} },
|
||||
|
||||
@@ -2717,13 +2717,13 @@ void MainFrame::init_menubar_as_editor()
|
||||
// BBS
|
||||
wxMenu *import_menu = new wxMenu();
|
||||
#ifndef __APPLE__
|
||||
append_menu_item(import_menu, wxID_ANY, _L("Import 3MF/STL/STEP/SVG/OBJ/AMF") + dots + "\t" + ctrl + "I", _L("Load a model"),
|
||||
append_menu_item(import_menu, wxID_ANY, _L("Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF") + dots + "\t" + ctrl + "I", _L("Load a model"),
|
||||
[this](wxCommandEvent&) { if (m_plater) {
|
||||
m_plater->add_file();
|
||||
} }, "menu_import", nullptr,
|
||||
[this](){return can_add_models(); }, this);
|
||||
#else
|
||||
append_menu_item(import_menu, wxID_ANY, _L("Import 3MF/STL/STEP/SVG/OBJ/AMF") + dots + "\t" + ctrl + "I", _L("Load a model"),
|
||||
append_menu_item(import_menu, wxID_ANY, _L("Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF") + dots + "\t" + ctrl + "I", _L("Load a model"),
|
||||
[this](wxCommandEvent&) { if (m_plater) { m_plater->add_model(); } }, "", nullptr,
|
||||
[this](){return can_add_models(); }, this);
|
||||
#endif
|
||||
|
||||
@@ -411,11 +411,18 @@ wxString sanitize_window_layout_for_wayland(const wxString& layout, bool* remove
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool is_color_import_choice_file(const std::string &path)
|
||||
{
|
||||
return boost::iends_with(path, ".obj") ||
|
||||
boost::iends_with(path, ".gltf") ||
|
||||
boost::iends_with(path, ".glb");
|
||||
}
|
||||
|
||||
static ObjImportMode show_obj_import_choice_dialog(wxWindow *parent, const ObjImportCapabilities &capabilities)
|
||||
{
|
||||
wxDialog dialog(parent ? parent : static_cast<wxWindow *>(wxGetApp().mainframe),
|
||||
wxID_ANY,
|
||||
_L("OBJ import"),
|
||||
_L("Color import"),
|
||||
wxDefaultPosition,
|
||||
wxDefaultSize,
|
||||
wxDEFAULT_DIALOG_STYLE);
|
||||
@@ -426,7 +433,7 @@ static ObjImportMode show_obj_import_choice_dialog(wxWindow *parent, const ObjIm
|
||||
line_top->SetBackgroundColour(wxColour(166, 169, 170));
|
||||
main_sizer->Add(line_top, 0, wxEXPAND, 0);
|
||||
|
||||
auto *message = new wxStaticText(&dialog, wxID_ANY, _L("Choose how to import this OBJ file."));
|
||||
auto *message = new wxStaticText(&dialog, wxID_ANY, _L("Choose how to import this model's color data."));
|
||||
message->Wrap(dialog.FromDIP(420));
|
||||
main_sizer->Add(message, 0, wxALL | wxEXPAND, dialog.FromDIP(16));
|
||||
|
||||
@@ -9271,7 +9278,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
bool obj_imported_for_texture_mapping = false;
|
||||
auto obj_color_fun = [this, &path](ObjDialogInOut &in_out) {
|
||||
|
||||
if (!boost::iends_with(path.string(), ".obj")) { return; }
|
||||
if (!is_color_import_choice_file(path.string())) { return; }
|
||||
const std::vector<std::string> extruder_colours = wxGetApp().plater()->get_extruder_colors_from_plater_config(nullptr, false);
|
||||
ObjColorDialog color_dlg(nullptr, in_out, extruder_colours);
|
||||
if (color_dlg.ShowModal() != wxID_OK) {
|
||||
@@ -9279,7 +9286,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
}
|
||||
};
|
||||
auto obj_import_mode_fun = [this, &path, &obj_imported_for_texture_mapping](const ObjImportCapabilities &capabilities) -> ObjImportMode {
|
||||
if (!boost::iends_with(path.string(), ".obj"))
|
||||
if (!is_color_import_choice_file(path.string()))
|
||||
return ObjImportMode::UseDefault;
|
||||
const ObjImportMode mode = show_obj_import_choice_dialog(q, capabilities);
|
||||
obj_imported_for_texture_mapping = mode == ObjImportMode::ImportTextures;
|
||||
@@ -11492,7 +11499,7 @@ void Plater::priv::reload_from_disk()
|
||||
const auto& path = input_paths[i].string();
|
||||
bool obj_imported_for_texture_mapping = false;
|
||||
auto obj_color_fun = [this, &path](ObjDialogInOut &in_out) {
|
||||
if (!boost::iends_with(path, ".obj")) { return; }
|
||||
if (!is_color_import_choice_file(path)) { return; }
|
||||
const std::vector<std::string> extruder_colours = wxGetApp().plater()->get_extruder_colors_from_plater_config(nullptr, false);
|
||||
ObjColorDialog color_dlg(nullptr, in_out, extruder_colours);
|
||||
if (color_dlg.ShowModal() != wxID_OK) {
|
||||
@@ -11500,7 +11507,7 @@ void Plater::priv::reload_from_disk()
|
||||
}
|
||||
};
|
||||
auto obj_import_mode_fun = [this, &path, &obj_imported_for_texture_mapping](const ObjImportCapabilities &capabilities) -> ObjImportMode {
|
||||
if (!boost::iends_with(path, ".obj"))
|
||||
if (!is_color_import_choice_file(path))
|
||||
return ObjImportMode::UseDefault;
|
||||
const ObjImportMode mode = show_obj_import_choice_dialog(q, capabilities);
|
||||
obj_imported_for_texture_mapping = mode == ObjImportMode::ImportTextures;
|
||||
|
||||
Reference in New Issue
Block a user