mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-27 19:01:02 +00:00
Merge main + clean up code + fix missing include
This commit is contained in:
@@ -0,0 +1,327 @@
|
||||
#include "AssimpImport.hpp"
|
||||
|
||||
#include "../TexturePainting.hpp"
|
||||
#include "ResourcePathUtils.hpp"
|
||||
|
||||
#include <assimp/Importer.hpp>
|
||||
#include <assimp/config.h>
|
||||
#include <assimp/material.h>
|
||||
#include <assimp/postprocess.h>
|
||||
#include <assimp/scene.h>
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace {
|
||||
|
||||
void clear_textured_mesh(TexturedMesh& out)
|
||||
{
|
||||
out.vertices.clear();
|
||||
out.indices.clear();
|
||||
out.uvs.clear();
|
||||
out.uv_coords.clear();
|
||||
out.uv_indices.clear();
|
||||
out.textures.clear();
|
||||
out.material_ids.clear();
|
||||
out.material_texture_map.clear();
|
||||
out.material_colors.clear();
|
||||
}
|
||||
|
||||
void set_error_message(std::string* error_message, const std::string& message)
|
||||
{
|
||||
if (error_message)
|
||||
*error_message = message;
|
||||
}
|
||||
|
||||
bool is_fbx_path(const std::string& path)
|
||||
{
|
||||
return boost::algorithm::iends_with(path, ".fbx");
|
||||
}
|
||||
|
||||
bool should_flip_uvs(const std::string& path)
|
||||
{
|
||||
return boost::algorithm::iends_with(path, ".fbx") ||
|
||||
boost::algorithm::iends_with(path, ".glb");
|
||||
}
|
||||
|
||||
unsigned int assimp_import_flags(const std::string& path)
|
||||
{
|
||||
unsigned int flags = aiProcess_Triangulate
|
||||
| aiProcess_GenNormals
|
||||
| aiProcess_PreTransformVertices
|
||||
| aiProcess_SortByPType;
|
||||
if (should_flip_uvs(path))
|
||||
flags |= aiProcess_FlipUVs;
|
||||
return flags;
|
||||
}
|
||||
|
||||
void configure_importer(Assimp::Importer& importer, const std::string& path, unsigned int flags)
|
||||
{
|
||||
importer.SetPropertyInteger(AI_CONFIG_PP_SBP_REMOVE,
|
||||
aiPrimitiveType_POINT | aiPrimitiveType_LINE);
|
||||
|
||||
if (flags & aiProcess_PreTransformVertices)
|
||||
importer.SetPropertyBool(AI_CONFIG_PP_PTV_KEEP_HIERARCHY, true);
|
||||
|
||||
if (is_fbx_path(path)) {
|
||||
importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ALL_GEOMETRY_LAYERS, true);
|
||||
importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_MATERIALS, true);
|
||||
importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_TEXTURES, true);
|
||||
importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ANIMATIONS, false);
|
||||
importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_LIGHTS, false);
|
||||
importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_CAMERAS, false);
|
||||
}
|
||||
}
|
||||
|
||||
bool read_external_texture_file(const boost::filesystem::path& path, TextureImage& out)
|
||||
{
|
||||
boost::nowide::ifstream file(path.string(), std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open())
|
||||
return false;
|
||||
|
||||
const std::streamoff size = file.tellg();
|
||||
if (size <= 0)
|
||||
return false;
|
||||
if (static_cast<uintmax_t>(size) > static_cast<uintmax_t>(std::numeric_limits<size_t>::max()))
|
||||
return false;
|
||||
|
||||
file.seekg(0);
|
||||
out.width = -1;
|
||||
out.height = -1;
|
||||
out.channels = 0;
|
||||
out.data.resize(static_cast<size_t>(size));
|
||||
file.read(reinterpret_cast<char*>(out.data.data()), size);
|
||||
if (!file && !file.eof()) {
|
||||
out.data.clear();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool read_embedded_texture(const aiTexture& texture, TextureImage& out)
|
||||
{
|
||||
out.data.clear();
|
||||
if (texture.mHeight == 0) {
|
||||
if (texture.mWidth == 0)
|
||||
return false;
|
||||
out.width = -1;
|
||||
out.height = -1;
|
||||
out.channels = 0;
|
||||
out.data.assign(
|
||||
reinterpret_cast<const unsigned char*>(texture.pcData),
|
||||
reinterpret_cast<const unsigned char*>(texture.pcData) + texture.mWidth);
|
||||
return !out.data.empty();
|
||||
}
|
||||
|
||||
if (texture.mWidth == 0 || texture.mHeight == 0)
|
||||
return false;
|
||||
if (texture.mWidth > static_cast<unsigned int>(std::numeric_limits<int>::max()) ||
|
||||
texture.mHeight > static_cast<unsigned int>(std::numeric_limits<int>::max())) {
|
||||
return false;
|
||||
}
|
||||
const size_t width = static_cast<size_t>(texture.mWidth);
|
||||
const size_t height = static_cast<size_t>(texture.mHeight);
|
||||
if (width > std::numeric_limits<size_t>::max() / height ||
|
||||
width * height > std::numeric_limits<size_t>::max() / 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
out.width = static_cast<int>(texture.mWidth);
|
||||
out.height = static_cast<int>(texture.mHeight);
|
||||
out.channels = 4;
|
||||
const size_t pixel_count = width * height;
|
||||
out.data.resize(pixel_count * 4);
|
||||
for (size_t i = 0; i < pixel_count; ++i) {
|
||||
const aiTexel& texel = texture.pcData[i];
|
||||
out.data[i * 4 + 0] = texel.r;
|
||||
out.data[i * 4 + 1] = texel.g;
|
||||
out.data[i * 4 + 2] = texel.b;
|
||||
out.data[i * 4 + 3] = texel.a;
|
||||
}
|
||||
return !out.data.empty();
|
||||
}
|
||||
|
||||
bool get_material_texture(const aiMaterial& material, aiString& texture_path)
|
||||
{
|
||||
if (material.GetTextureCount(aiTextureType_DIFFUSE) > 0 &&
|
||||
material.GetTexture(aiTextureType_DIFFUSE, 0, &texture_path) == AI_SUCCESS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (material.GetTextureCount(aiTextureType_BASE_COLOR) > 0 &&
|
||||
material.GetTexture(aiTextureType_BASE_COLOR, 0, &texture_path) == AI_SUCCESS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::array<float, 4> get_material_color(const aiMaterial& material)
|
||||
{
|
||||
aiColor4D color(1.f, 1.f, 1.f, 1.f);
|
||||
if (material.Get(AI_MATKEY_BASE_COLOR, color) == AI_SUCCESS)
|
||||
return {color.r, color.g, color.b, color.a};
|
||||
if (material.Get(AI_MATKEY_COLOR_DIFFUSE, color) == AI_SUCCESS)
|
||||
return {color.r, color.g, color.b, color.a};
|
||||
return {1.f, 1.f, 1.f, 1.f};
|
||||
}
|
||||
|
||||
bool collect_mesh(const aiMesh& mesh, size_t& vertex_offset, TexturedMesh& out, std::string& error)
|
||||
{
|
||||
if (mesh.mNumVertices > static_cast<size_t>(std::numeric_limits<int>::max()) - vertex_offset) {
|
||||
error = "Assimp mesh has too many vertices for TexturedMesh indices";
|
||||
return false;
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < mesh.mNumVertices; ++i) {
|
||||
const aiVector3D& v = mesh.mVertices[i];
|
||||
out.vertices.push_back({v.x, v.y, v.z});
|
||||
|
||||
if (mesh.HasTextureCoords(0)) {
|
||||
const aiVector3D& uv = mesh.mTextureCoords[0][i];
|
||||
out.uvs.push_back({uv.x, uv.y});
|
||||
} else {
|
||||
out.uvs.push_back({0.f, 0.f});
|
||||
}
|
||||
}
|
||||
|
||||
const int material_index = static_cast<int>(mesh.mMaterialIndex);
|
||||
for (unsigned int i = 0; i < mesh.mNumFaces; ++i) {
|
||||
const aiFace& face = mesh.mFaces[i];
|
||||
if (face.mNumIndices != 3)
|
||||
continue;
|
||||
if (face.mIndices[0] >= mesh.mNumVertices ||
|
||||
face.mIndices[1] >= mesh.mNumVertices ||
|
||||
face.mIndices[2] >= mesh.mNumVertices) {
|
||||
error = "Assimp mesh face index is out of bounds";
|
||||
return false;
|
||||
}
|
||||
out.indices.push_back({
|
||||
static_cast<int>(static_cast<size_t>(face.mIndices[0]) + vertex_offset),
|
||||
static_cast<int>(static_cast<size_t>(face.mIndices[1]) + vertex_offset),
|
||||
static_cast<int>(static_cast<size_t>(face.mIndices[2]) + vertex_offset)});
|
||||
out.material_ids.push_back(material_index);
|
||||
}
|
||||
|
||||
vertex_offset += mesh.mNumVertices;
|
||||
return true;
|
||||
}
|
||||
|
||||
void collect_materials(const aiScene& scene, const boost::filesystem::path& base_dir, TexturedMesh& out)
|
||||
{
|
||||
out.material_texture_map.assign(scene.mNumMaterials, -1);
|
||||
out.material_colors.assign(scene.mNumMaterials, {1.f, 1.f, 1.f, 1.f});
|
||||
|
||||
for (unsigned int material_index = 0; material_index < scene.mNumMaterials; ++material_index) {
|
||||
const aiMaterial* material = scene.mMaterials[material_index];
|
||||
if (!material)
|
||||
continue;
|
||||
|
||||
out.material_colors[material_index] = get_material_color(*material);
|
||||
|
||||
aiString texture_path;
|
||||
if (!get_material_texture(*material, texture_path))
|
||||
continue;
|
||||
|
||||
TextureImage image;
|
||||
const aiTexture* embedded_texture = scene.GetEmbeddedTexture(texture_path.C_Str());
|
||||
if (embedded_texture) {
|
||||
if (!read_embedded_texture(*embedded_texture, image))
|
||||
continue;
|
||||
} else {
|
||||
const boost::filesystem::path resolved = resource_path::resolve_external_resource_path(
|
||||
base_dir, texture_path.C_Str(), "Assimp texture");
|
||||
if (resolved.empty()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "AssimpImport: texture file not found: "
|
||||
<< texture_path.C_Str();
|
||||
continue;
|
||||
}
|
||||
if (!read_external_texture_file(resolved, image)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "AssimpImport: failed to read texture: "
|
||||
<< resolved;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
out.material_texture_map[material_index] = static_cast<int>(out.textures.size());
|
||||
out.textures.push_back(std::move(image));
|
||||
}
|
||||
}
|
||||
|
||||
std::string scene_failure_summary(const std::string& path, const char* assimp_error)
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss << "Assimp failed to import " << path;
|
||||
if (assimp_error && assimp_error[0] != '\0')
|
||||
ss << ": " << assimp_error;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool load_assimp_textured_model(const std::string& path, TexturedMesh& out, std::string* error_message)
|
||||
{
|
||||
clear_textured_mesh(out);
|
||||
|
||||
Assimp::Importer importer;
|
||||
const unsigned int flags = assimp_import_flags(path);
|
||||
configure_importer(importer, path, flags);
|
||||
|
||||
const aiScene* scene = importer.ReadFile(path, flags);
|
||||
if (!scene || (scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE) || !scene->mRootNode) {
|
||||
const std::string message = scene_failure_summary(path, importer.GetErrorString());
|
||||
BOOST_LOG_TRIVIAL(error) << "AssimpImport: " << message;
|
||||
set_error_message(error_message, message);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (scene->mNumMeshes == 0) {
|
||||
const std::string message = "Assimp scene has no meshes: " + path;
|
||||
BOOST_LOG_TRIVIAL(error) << "AssimpImport: " << message;
|
||||
set_error_message(error_message, message);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t vertex_offset = 0;
|
||||
for (unsigned int mesh_index = 0; mesh_index < scene->mNumMeshes; ++mesh_index) {
|
||||
const aiMesh* mesh = scene->mMeshes[mesh_index];
|
||||
if (!mesh || !mesh->HasPositions())
|
||||
continue;
|
||||
std::string mesh_error;
|
||||
if (!collect_mesh(*mesh, vertex_offset, out, mesh_error)) {
|
||||
const std::string message = mesh_error + ": " + path;
|
||||
BOOST_LOG_TRIVIAL(error) << "AssimpImport: " << message;
|
||||
set_error_message(error_message, message);
|
||||
clear_textured_mesh(out);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (out.vertices.empty() || out.indices.empty()) {
|
||||
const std::string message = "Assimp extracted no valid triangles: " + path;
|
||||
BOOST_LOG_TRIVIAL(error) << "AssimpImport: " << message;
|
||||
set_error_message(error_message, message);
|
||||
clear_textured_mesh(out);
|
||||
return false;
|
||||
}
|
||||
|
||||
collect_materials(*scene, boost::filesystem::path(path).parent_path(), out);
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "AssimpImport: loaded " << out.vertices.size()
|
||||
<< " vertices, " << out.indices.size()
|
||||
<< " triangles, " << out.textures.size()
|
||||
<< " textures from " << path;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
struct TexturedMesh;
|
||||
|
||||
bool load_assimp_textured_model(const std::string& path, TexturedMesh& out, std::string* error_message = nullptr);
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "../libslic3r.h"
|
||||
#include "../Model.hpp"
|
||||
#include "../TriangleMesh.hpp"
|
||||
#include "../TexturePainting.hpp"
|
||||
#include "ResourcePathUtils.hpp"
|
||||
|
||||
#include "OBJ.hpp"
|
||||
#include "objparser.hpp"
|
||||
@@ -8,6 +10,7 @@
|
||||
#include <string>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define DIR_SEPARATOR '\\'
|
||||
@@ -21,7 +24,7 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::string &message)
|
||||
bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::string &message, ObjParser::MtlData *out_mtl)
|
||||
{
|
||||
if (meshptr == nullptr)
|
||||
return false;
|
||||
@@ -98,6 +101,7 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s
|
||||
its.indices.reserve(num_faces + num_quads);
|
||||
if (exist_mtl) {
|
||||
obj_info.is_single_mtl = data.usemtls.size() == 1 && mtl_data.new_mtl_unmap.size() == 1;
|
||||
obj_info.usemtls = data.usemtls;
|
||||
obj_info.face_colors.reserve(num_faces + num_quads);
|
||||
}
|
||||
bool has_color = data.has_vertex_color;
|
||||
@@ -210,14 +214,17 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s
|
||||
}
|
||||
if (meshptr->volume() < 0)
|
||||
meshptr->flip_triangles();
|
||||
// Hand the parsed material table back so callers can build a TexturedMesh from it.
|
||||
if (out_mtl)
|
||||
*out_mtl = mtl_data;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool load_obj(const char *path, Model *model, ObjInfo& obj_info, std::string &message, const char *object_name_in)
|
||||
bool load_obj(const char *path, Model *model, ObjInfo& obj_info, std::string &message, const char *object_name_in, ObjParser::MtlData *out_mtl)
|
||||
{
|
||||
TriangleMesh mesh;
|
||||
|
||||
bool ret = load_obj(path, &mesh, obj_info, message);
|
||||
bool ret = load_obj(path, &mesh, obj_info, message, out_mtl);
|
||||
|
||||
if (ret) {
|
||||
std::string object_name;
|
||||
@@ -232,6 +239,144 @@ bool load_obj(const char *path, Model *model, ObjInfo& obj_info, std::string &me
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool obj_to_textured_mesh(
|
||||
const ObjInfo& obj_info,
|
||||
const indexed_triangle_set& its,
|
||||
const ObjParser::MtlData& mtl_data,
|
||||
const std::string& obj_directory,
|
||||
TexturedMesh& out)
|
||||
{
|
||||
if (its.vertices.empty() || its.indices.empty() || !obj_info.has_uv_png)
|
||||
return false;
|
||||
|
||||
const size_t nv = its.vertices.size();
|
||||
const size_t nf = its.indices.size();
|
||||
|
||||
// 1. Copy vertices
|
||||
out.vertices.resize(nv);
|
||||
for (size_t i = 0; i < nv; ++i)
|
||||
out.vertices[i] = {its.vertices[i].x(), its.vertices[i].y(), its.vertices[i].z()};
|
||||
|
||||
// 2. Copy face indices
|
||||
out.indices.resize(nf);
|
||||
for (size_t i = 0; i < nf; ++i)
|
||||
out.indices[i] = {its.indices[i][0], its.indices[i][1], its.indices[i][2]};
|
||||
|
||||
// 3. Build per-face UV (uv_coords + uv_indices)
|
||||
// OBJ UV convention: V=0 at bottom (OpenGL); texture sampling expects V=0 at top (like glTF/OpenCV).
|
||||
// Flip V here so downstream code works uniformly.
|
||||
if (!obj_info.uvs.empty()) {
|
||||
const size_t uv_face_count = obj_info.uvs.size();
|
||||
out.uv_coords.resize(uv_face_count * 3);
|
||||
out.uv_indices.resize(nf);
|
||||
for (size_t fi = 0; fi < nf; ++fi) {
|
||||
if (fi < uv_face_count) {
|
||||
int base = static_cast<int>(fi * 3);
|
||||
out.uv_coords[base + 0] = {obj_info.uvs[fi][0].x(), 1.f - obj_info.uvs[fi][0].y()};
|
||||
out.uv_coords[base + 1] = {obj_info.uvs[fi][1].x(), 1.f - obj_info.uvs[fi][1].y()};
|
||||
out.uv_coords[base + 2] = {obj_info.uvs[fi][2].x(), 1.f - obj_info.uvs[fi][2].y()};
|
||||
out.uv_indices[fi] = {base, base + 1, base + 2};
|
||||
} else {
|
||||
out.uv_indices[fi] = {0, 0, 0};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Build material list and load textures from disk
|
||||
// Map: material name -> material index
|
||||
std::map<std::string, int> mtl_name_to_idx;
|
||||
for (size_t i = 0; i < mtl_data.mtl_orders.size(); ++i)
|
||||
mtl_name_to_idx[mtl_data.mtl_orders[i]] = static_cast<int>(i);
|
||||
|
||||
const int num_materials = static_cast<int>(mtl_data.mtl_orders.size());
|
||||
out.material_colors.resize(num_materials, {1.f, 1.f, 1.f, 1.f});
|
||||
out.material_texture_map.resize(num_materials, -1);
|
||||
|
||||
// Map: texture filename -> index in out.textures
|
||||
std::map<std::string, int> png_to_tex_idx;
|
||||
|
||||
for (int mi = 0; mi < num_materials; ++mi) {
|
||||
const std::string& name = mtl_data.mtl_orders[mi];
|
||||
auto it = mtl_data.new_mtl_unmap.find(name);
|
||||
if (it == mtl_data.new_mtl_unmap.end())
|
||||
continue;
|
||||
const auto& mtl = *(it->second);
|
||||
|
||||
// Material color from Kd
|
||||
out.material_colors[mi] = {mtl.Kd[0], mtl.Kd[1], mtl.Kd[2], mtl.Tr};
|
||||
|
||||
// Texture from map_Kd
|
||||
if (mtl.map_Kd.empty())
|
||||
continue;
|
||||
|
||||
auto tex_it = png_to_tex_idx.find(mtl.map_Kd);
|
||||
if (tex_it != png_to_tex_idx.end()) {
|
||||
out.material_texture_map[mi] = tex_it->second;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Resolve texture file path.
|
||||
const boost::filesystem::path requested_tex_path(mtl.map_Kd);
|
||||
const boost::filesystem::path tex_path = requested_tex_path.is_absolute() ?
|
||||
resource_path::resolve_existing_path_case_insensitive(requested_tex_path, "obj_to_textured_mesh: map_Kd") :
|
||||
resource_path::resolve_existing_relative_path_case_insensitive(
|
||||
boost::filesystem::path(obj_directory), requested_tex_path, "obj_to_textured_mesh: map_Kd");
|
||||
|
||||
if (tex_path.empty()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "obj_to_textured_mesh: texture not found: " << requested_tex_path;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read raw file bytes
|
||||
boost::nowide::ifstream file(tex_path.string(), std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open())
|
||||
continue;
|
||||
auto file_size = file.tellg();
|
||||
if (file_size <= 0)
|
||||
continue;
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
TextureImage ti;
|
||||
ti.data.resize(static_cast<size_t>(file_size));
|
||||
file.read(reinterpret_cast<char*>(ti.data.data()), file_size);
|
||||
ti.width = -1;
|
||||
ti.height = -1;
|
||||
ti.channels = 0;
|
||||
|
||||
int new_idx = static_cast<int>(out.textures.size());
|
||||
out.textures.push_back(std::move(ti));
|
||||
png_to_tex_idx[mtl.map_Kd] = new_idx;
|
||||
out.material_texture_map[mi] = new_idx;
|
||||
}
|
||||
|
||||
// 5. Build per-face material_ids from usemtls ranges
|
||||
out.material_ids.resize(nf, -1);
|
||||
if (!obj_info.usemtls.empty()) {
|
||||
for (size_t fi = 0; fi < nf; ++fi) {
|
||||
int face_idx = static_cast<int>(fi);
|
||||
for (size_t k = 0; k < obj_info.usemtls.size(); ++k) {
|
||||
const auto& um = obj_info.usemtls[k];
|
||||
if (face_idx >= um.face_start && face_idx <= um.face_end) {
|
||||
auto name_it = mtl_name_to_idx.find(um.name);
|
||||
if (name_it != mtl_name_to_idx.end())
|
||||
out.material_ids[fi] = name_it->second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (out.textures.empty()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "obj_to_textured_mesh: no textures loaded";
|
||||
return false;
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "obj_to_textured_mesh: " << nf << " faces, "
|
||||
<< out.textures.size() << " textures, "
|
||||
<< num_materials << " materials";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool store_obj(const char *path, TriangleMesh *mesh)
|
||||
{
|
||||
//FIXME returning false even if write failed.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef slic3r_Format_OBJ_hpp_
|
||||
#define slic3r_Format_OBJ_hpp_
|
||||
#include "libslic3r/Color.hpp"
|
||||
#include "objparser.hpp"
|
||||
#include <unordered_map>
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -18,6 +19,7 @@ struct ObjInfo {
|
||||
std::map<std::string,bool> pngs;
|
||||
std::unordered_map<int, std::string> uv_map_pngs;
|
||||
bool has_uv_png{false};
|
||||
std::vector<ObjParser::ObjUseMtl> usemtls; // material spans, for texture import
|
||||
|
||||
};
|
||||
struct ObjDialogInOut
|
||||
@@ -32,8 +34,18 @@ struct ObjDialogInOut
|
||||
std::string lost_material_name{""};
|
||||
};
|
||||
typedef std::function<void(ObjDialogInOut &in_out)> ObjImportColorFn;
|
||||
extern bool load_obj(const char *path, TriangleMesh *mesh, ObjInfo &vertex_colors, std::string &message);
|
||||
extern bool load_obj(const char *path, Model *model, ObjInfo &vertex_colors, std::string &message, const char *object_name = nullptr);
|
||||
extern bool load_obj(const char *path, TriangleMesh *mesh, ObjInfo &vertex_colors, std::string &message, ObjParser::MtlData *out_mtl = nullptr);
|
||||
extern bool load_obj(const char *path, Model *model, ObjInfo &vertex_colors, std::string &message, const char *object_name = nullptr, ObjParser::MtlData *out_mtl = nullptr);
|
||||
|
||||
struct TexturedMesh;
|
||||
// Build a TexturedMesh (vertices + per-face UVs + the texture files named by map_Kd) from a
|
||||
// parsed OBJ plus its material table, so the texture-to-color importer can sample face colours.
|
||||
extern bool obj_to_textured_mesh(
|
||||
const ObjInfo& obj_info,
|
||||
const indexed_triangle_set& its,
|
||||
const ObjParser::MtlData& mtl_data,
|
||||
const std::string& obj_directory,
|
||||
TexturedMesh& out);
|
||||
|
||||
extern bool store_obj(const char *path, TriangleMesh *mesh);
|
||||
extern bool store_obj(const char *path, ModelObject *model);
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
#ifndef slic3r_Format_ResourcePathUtils_hpp_
|
||||
#define slic3r_Format_ResourcePathUtils_hpp_
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace resource_path {
|
||||
|
||||
inline std::string ascii_lower_copy(const std::string& value)
|
||||
{
|
||||
std::string lowered;
|
||||
lowered.reserve(value.size());
|
||||
for (unsigned char ch : value)
|
||||
lowered.push_back(static_cast<char>(std::tolower(ch)));
|
||||
return lowered;
|
||||
}
|
||||
|
||||
inline boost::filesystem::path portable_path_copy(const boost::filesystem::path& value)
|
||||
{
|
||||
std::string portable = value.string();
|
||||
std::replace(portable.begin(), portable.end(), '\\', '/');
|
||||
return boost::filesystem::path(portable);
|
||||
}
|
||||
|
||||
inline int hex_digit_value(char ch)
|
||||
{
|
||||
if (ch >= '0' && ch <= '9') return ch - '0';
|
||||
if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10;
|
||||
if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Byte-level percent decoding. Per RFC 3986 the %XX byte stream is expected to be
|
||||
// UTF-8 when produced from URIs / Assimp aiString; this function performs no
|
||||
// transcoding, so callers must treat both input and output as raw UTF-8 bytes.
|
||||
inline std::string percent_decode_copy(const std::string& value)
|
||||
{
|
||||
std::string decoded;
|
||||
decoded.reserve(value.size());
|
||||
for (std::size_t i = 0; i < value.size(); ++i) {
|
||||
if (value[i] == '%' && i + 2 < value.size()) {
|
||||
const int hi = hex_digit_value(value[i + 1]);
|
||||
const int lo = hex_digit_value(value[i + 2]);
|
||||
if (hi >= 0 && lo >= 0) {
|
||||
decoded.push_back(static_cast<char>((hi << 4) | lo));
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
decoded.push_back(value[i]);
|
||||
}
|
||||
return decoded;
|
||||
}
|
||||
|
||||
inline std::string strip_file_uri_prefix_copy(const std::string& value)
|
||||
{
|
||||
const std::string lower = ascii_lower_copy(value);
|
||||
if (lower.rfind("file://", 0) != 0)
|
||||
return value;
|
||||
|
||||
std::string path = value.substr(7);
|
||||
if (ascii_lower_copy(path).rfind("localhost/", 0) == 0)
|
||||
path.erase(0, std::string("localhost").size());
|
||||
else if (!path.empty() && path.front() != '/')
|
||||
path = "//" + path;
|
||||
|
||||
// file:///C:/... should become C:/..., while file:///tmp/... keeps /tmp/...
|
||||
if (path.size() >= 3 && path[0] == '/' && std::isalpha(static_cast<unsigned char>(path[1])) && path[2] == ':')
|
||||
path.erase(path.begin());
|
||||
return path;
|
||||
}
|
||||
|
||||
inline bool file_uri_has_remote_authority(const std::string& value)
|
||||
{
|
||||
const std::string lower = ascii_lower_copy(value);
|
||||
if (lower.rfind("file://", 0) != 0)
|
||||
return false;
|
||||
|
||||
const std::string path = value.substr(7);
|
||||
if (path.empty() || path.front() == '/')
|
||||
return false;
|
||||
|
||||
const std::size_t slash = path.find('/');
|
||||
const std::string authority = path.substr(0, slash);
|
||||
return ascii_lower_copy(authority) != "localhost";
|
||||
}
|
||||
|
||||
inline bool looks_like_windows_absolute_path(const boost::filesystem::path& path)
|
||||
{
|
||||
const std::string portable = portable_path_copy(path).string();
|
||||
return portable.size() >= 3
|
||||
&& std::isalpha(static_cast<unsigned char>(portable[0]))
|
||||
&& portable[1] == ':'
|
||||
&& portable[2] == '/';
|
||||
}
|
||||
|
||||
inline boost::filesystem::path filename_from_portable_path(const boost::filesystem::path& value)
|
||||
{
|
||||
const boost::filesystem::path portable = portable_path_copy(value);
|
||||
return portable.filename();
|
||||
}
|
||||
|
||||
inline boost::filesystem::path find_child_case_insensitive(
|
||||
const boost::filesystem::path& directory,
|
||||
const boost::filesystem::path& requested_name,
|
||||
const char* context)
|
||||
{
|
||||
if (!boost::filesystem::exists(directory) || !boost::filesystem::is_directory(directory))
|
||||
return {};
|
||||
|
||||
const std::string requested_lower = ascii_lower_copy(requested_name.filename().string());
|
||||
std::vector<boost::filesystem::path> matches;
|
||||
|
||||
boost::system::error_code ec;
|
||||
for (boost::filesystem::directory_iterator it(directory, ec), end; !ec && it != end; it.increment(ec)) {
|
||||
if (ascii_lower_copy(it->path().filename().string()) == requested_lower)
|
||||
matches.push_back(it->path());
|
||||
}
|
||||
|
||||
if (matches.size() == 1)
|
||||
return matches.front();
|
||||
|
||||
if (matches.size() > 1) {
|
||||
BOOST_LOG_TRIVIAL(warning) << context << ": ambiguous case-insensitive resource match for "
|
||||
<< requested_name << " in " << directory;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
inline boost::filesystem::path resolve_existing_path_case_insensitive(
|
||||
const boost::filesystem::path& requested_path,
|
||||
const char* context = "resource_path")
|
||||
{
|
||||
const boost::filesystem::path normalized_path = portable_path_copy(requested_path);
|
||||
|
||||
if (normalized_path.empty())
|
||||
return {};
|
||||
|
||||
if (boost::filesystem::exists(normalized_path))
|
||||
return normalized_path;
|
||||
|
||||
boost::filesystem::path current;
|
||||
bool initialized = false;
|
||||
|
||||
for (const boost::filesystem::path& part : normalized_path) {
|
||||
if (part == normalized_path.root_name() || part == normalized_path.root_directory()) {
|
||||
current /= part;
|
||||
initialized = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!initialized) {
|
||||
current = boost::filesystem::current_path();
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
boost::filesystem::path exact = current / part;
|
||||
if (boost::filesystem::exists(exact)) {
|
||||
current = exact;
|
||||
continue;
|
||||
}
|
||||
|
||||
boost::filesystem::path matched = find_child_case_insensitive(current, part, context);
|
||||
if (matched.empty())
|
||||
return {};
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << context << ": resolved resource path case-insensitively from "
|
||||
<< exact << " to " << matched;
|
||||
current = matched;
|
||||
}
|
||||
|
||||
return boost::filesystem::exists(current) ? current : boost::filesystem::path();
|
||||
}
|
||||
|
||||
inline boost::filesystem::path resolve_existing_relative_path_case_insensitive(
|
||||
const boost::filesystem::path& base_dir,
|
||||
const boost::filesystem::path& resource_path,
|
||||
const char* context = "resource_path")
|
||||
{
|
||||
const boost::filesystem::path requested = resource_path.is_absolute() ? resource_path : base_dir / resource_path;
|
||||
return resolve_existing_path_case_insensitive(requested, context);
|
||||
}
|
||||
|
||||
// Resolve a resource path that originated outside our own code (e.g. a glTF/FBX
|
||||
// material texture reference or a file:// URI inside a 3MF descriptor).
|
||||
//
|
||||
// `raw_path` is expected to be UTF-8 regardless of host platform: file URIs are
|
||||
// UTF-8 by spec, and Assimp aiString uses UTF-8 internally. Cross-platform
|
||||
// correctness on Windows additionally relies on the process having called
|
||||
// boost::nowide::nowide_filesystem() during startup (see src/BambuStudio.cpp),
|
||||
// which imbues boost::filesystem::path with a UTF-8 codecvt so that
|
||||
// `path(std::string)` constructs from UTF-8 byte sequences. Callers that bypass
|
||||
// the main entry point (standalone CLI tools, unit tests) must reproduce that
|
||||
// setup themselves before invoking this helper.
|
||||
inline boost::filesystem::path resolve_external_resource_path(
|
||||
const boost::filesystem::path& base_dir,
|
||||
const std::string& raw_path,
|
||||
const char* context = "resource_path",
|
||||
bool allow_basename_fallback = true)
|
||||
{
|
||||
if (raw_path.empty())
|
||||
return {};
|
||||
|
||||
const bool remote_file_uri = file_uri_has_remote_authority(raw_path);
|
||||
const std::string decoded_path = percent_decode_copy(strip_file_uri_prefix_copy(raw_path));
|
||||
const boost::filesystem::path requested = portable_path_copy(boost::filesystem::path(decoded_path));
|
||||
|
||||
boost::filesystem::path resolved = (requested.is_absolute() || looks_like_windows_absolute_path(requested)) ?
|
||||
resolve_existing_path_case_insensitive(requested, context) :
|
||||
resolve_existing_relative_path_case_insensitive(base_dir, requested, context);
|
||||
if (!resolved.empty())
|
||||
return resolved;
|
||||
|
||||
if (!allow_basename_fallback || remote_file_uri)
|
||||
return {};
|
||||
|
||||
const boost::filesystem::path basename = filename_from_portable_path(requested);
|
||||
if (basename.empty())
|
||||
return {};
|
||||
|
||||
resolved = resolve_existing_relative_path_case_insensitive(base_dir, basename, context);
|
||||
if (!resolved.empty()) {
|
||||
BOOST_LOG_TRIVIAL(info) << context << ": resolved resource by basename from "
|
||||
<< requested << " to " << resolved;
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
} // namespace resource_path
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_Format_ResourcePathUtils_hpp_ */
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "../Preset.hpp"
|
||||
#include "../Utils.hpp"
|
||||
#include "../LocalesUtils.hpp"
|
||||
#include "../FilamentMixer.hpp"
|
||||
#include "../GCode.hpp"
|
||||
#include "../Geometry.hpp"
|
||||
#include "../GCode/ThumbnailData.hpp"
|
||||
@@ -246,6 +247,8 @@ static constexpr const char* BUILD_TAG = "build";
|
||||
static constexpr const char* ITEM_TAG = "item";
|
||||
static constexpr const char* METADATA_TAG = "metadata";
|
||||
static constexpr const char* FILAMENT_TAG = "filament";
|
||||
static constexpr const char* MIXED_FILAMENT_TAG = "mixed_filament";
|
||||
static constexpr const char* MIXED_FILAMENT_COMPONENTS_TAG = "components";
|
||||
static constexpr const char* SLICE_WARNING_TAG = "warning";
|
||||
static constexpr const char* WARNING_MSG_TAG = "msg";
|
||||
static constexpr const char *FILAMENT_ID_TAG = "id";
|
||||
@@ -1334,6 +1337,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
bool _handle_end_config_metadata();
|
||||
|
||||
bool _handle_start_config_filament(const char** attributes, unsigned int num_attributes);
|
||||
bool _handle_start_config_mixed_filament(const char** attributes, unsigned int num_attributes);
|
||||
bool _handle_end_config_filament();
|
||||
|
||||
bool _handle_start_config_warning(const char** attributes, unsigned int num_attributes);
|
||||
@@ -2713,6 +2717,14 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
return;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", load project config file successfully from %1%\n") %dest_file;
|
||||
|
||||
// Heal any gradient-curve slots corrupted by the legacy "|" separator collision
|
||||
// (see FilamentMixer::sanitize_mixed_gradient_curve_array). The 3MF JSON itself
|
||||
// is safe (";" + C-style escape), but older projects saved through the buggy
|
||||
// export_selections/load_selections path may already carry single-point entries
|
||||
// that fail MakerWorld's "curve needs >= 2 points" check.
|
||||
if (auto* curve_opt = config.option<ConfigOptionStrings>("filament_mixed_gradient_curve"))
|
||||
Slic3r::sanitize_mixed_gradient_curve_array(curve_opt->values);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3530,6 +3542,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
res = _handle_start_config_plater_instance(attributes, num_attributes);
|
||||
else if (::strcmp(FILAMENT_TAG, name) == 0)
|
||||
res = _handle_start_config_filament(attributes, num_attributes);
|
||||
else if (::strcmp(MIXED_FILAMENT_TAG, name) == 0)
|
||||
res = _handle_start_config_mixed_filament(attributes, num_attributes);
|
||||
else if (::strcmp(SLICE_WARNING_TAG, name) == 0)
|
||||
res = _handle_start_config_warning(attributes, num_attributes);
|
||||
else if (::strcmp(NOZZLE_TAG, name) == 0)
|
||||
@@ -4703,6 +4717,23 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _BBS_3MF_Importer::_handle_start_config_mixed_filament(const char** attributes, unsigned int num_attributes)
|
||||
{
|
||||
if (m_curr_plater) {
|
||||
std::string id = bbs_get_attribute_value_string(attributes, num_attributes, FILAMENT_ID_TAG);
|
||||
std::string type = bbs_get_attribute_value_string(attributes, num_attributes, FILAMENT_TYPE_TAG);
|
||||
std::string color = bbs_get_attribute_value_string(attributes, num_attributes, FILAMENT_COLOR_TAG);
|
||||
std::string components = bbs_get_attribute_value_string(attributes, num_attributes, MIXED_FILAMENT_COMPONENTS_TAG);
|
||||
PlateMixedFilamentInfo mixed_info;
|
||||
mixed_info.id = atoi(id.c_str());
|
||||
mixed_info.type = type;
|
||||
mixed_info.color = color;
|
||||
mixed_info.components = components;
|
||||
m_curr_plater->mixed_filaments_info.push_back(mixed_info);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _BBS_3MF_Importer::_handle_end_config_filament()
|
||||
{
|
||||
// do nothing
|
||||
@@ -8520,6 +8551,17 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
<< FILAMENT_USED_FOR_SUPPORT << "=\"" << std::boolalpha << it->used_for_support << "\"/>\n";
|
||||
}
|
||||
|
||||
// Mixed (virtual) filaments used by this plate. These are resolved to physical
|
||||
// components before g-code statistics, so they are not present in the <filament>
|
||||
// list above and are recorded separately here.
|
||||
for (auto it = plate_data->mixed_filaments_info.begin(); it != plate_data->mixed_filaments_info.end(); it++)
|
||||
{
|
||||
stream << " <" << MIXED_FILAMENT_TAG << " " << FILAMENT_ID_TAG << "=\"" << std::to_string(it->id) << "\" "
|
||||
<< FILAMENT_TYPE_TAG << "=\"" << it->type << "\" "
|
||||
<< FILAMENT_COLOR_TAG << "=\"" << it->color << "\" "
|
||||
<< MIXED_FILAMENT_COMPONENTS_TAG << "=\"" << it->components << "\"/>\n";
|
||||
}
|
||||
|
||||
for (auto it = plate_data->warnings.begin(); it != plate_data->warnings.end(); it++) {
|
||||
stream << " <" << SLICE_WARNING_TAG << " msg=\"" << it->msg << "\" level=\"" << std::to_string(it->level) << "\" error_code =\"" << it->error_code << "\" />\n";
|
||||
}
|
||||
|
||||
@@ -48,6 +48,18 @@ public:
|
||||
};
|
||||
|
||||
|
||||
// Mixed (virtual) filament used by a plate. Mixed filaments are virtual slots that get
|
||||
// resolved to their physical components before g-code statistics, so they never appear in
|
||||
// slice_filaments_info. They are recorded here separately so a plate's mixed-color usage
|
||||
// can be recovered from slice_info.
|
||||
struct PlateMixedFilamentInfo
|
||||
{
|
||||
int id{0}; // 1-based virtual filament slot id
|
||||
std::string type;
|
||||
std::string color; // blended display color, "#RRGGBB"
|
||||
std::string components; // 1-based physical component ids, comma separated, e.g. "1,3"
|
||||
};
|
||||
|
||||
//BBS: define plate data list related structures
|
||||
struct PlateData
|
||||
{
|
||||
@@ -89,6 +101,8 @@ struct PlateData
|
||||
std::string first_layer_time;
|
||||
std::string plate_name;
|
||||
std::vector<FilamentInfo> slice_filaments_info;
|
||||
// Mixed (virtual) filaments used by this plate; empty when no mixed filament is used.
|
||||
std::vector<PlateMixedFilamentInfo> mixed_filaments_info;
|
||||
std::vector<size_t> skipped_objects;
|
||||
DynamicPrintConfig config;
|
||||
bool is_support_used {false};
|
||||
|
||||
@@ -262,12 +262,9 @@ static bool obj_parseline(const char *line, ObjData &data)
|
||||
}
|
||||
face_index_count++;
|
||||
}
|
||||
if (face_index_count == 3) {//tri
|
||||
data.usemtls.back().face_end++;
|
||||
} else if (face_index_count == 4) {//quad
|
||||
data.usemtls.back().face_end++;
|
||||
data.usemtls.back().face_end++;
|
||||
}
|
||||
if (face_index_count >= 3) {
|
||||
data.usemtls.back().face_end += face_index_count - 2;
|
||||
}
|
||||
}
|
||||
vertex.coordIdx = -1;
|
||||
vertex.normalIdx = -1;
|
||||
@@ -374,6 +371,107 @@ static bool obj_parseline(const char *line, ObjData &data)
|
||||
return true;
|
||||
}
|
||||
static std::string cur_mtl_name = "";
|
||||
static bool mtl_is_space(char c)
|
||||
{
|
||||
return c == ' ' || c == '\t' || c == '\r';
|
||||
}
|
||||
|
||||
static const char* mtl_skip_ws(const char *line)
|
||||
{
|
||||
while (mtl_is_space(*line))
|
||||
++line;
|
||||
return line;
|
||||
}
|
||||
|
||||
static const char* mtl_skip_token(const char *line)
|
||||
{
|
||||
while (*line != 0 && !mtl_is_space(*line))
|
||||
++line;
|
||||
return line;
|
||||
}
|
||||
|
||||
static bool mtl_token_equals(const char *begin, const char *end, const char *token)
|
||||
{
|
||||
const size_t len = static_cast<size_t>(end - begin);
|
||||
return strlen(token) == len && strncmp(begin, token, len) == 0;
|
||||
}
|
||||
|
||||
static std::string mtl_trim_value(const char *line)
|
||||
{
|
||||
const char *begin = mtl_skip_ws(line);
|
||||
const char *end = begin + strlen(begin);
|
||||
while (end > begin && mtl_is_space(*(end - 1)))
|
||||
--end;
|
||||
return std::string(begin, end);
|
||||
}
|
||||
|
||||
static bool mtl_skip_numeric_token(const char *&line)
|
||||
{
|
||||
const char *begin = mtl_skip_ws(line);
|
||||
if (*begin == 0)
|
||||
return false;
|
||||
char *endptr = 0;
|
||||
strtod(begin, &endptr);
|
||||
if (endptr == begin || (!mtl_is_space(*endptr) && *endptr != 0))
|
||||
return false;
|
||||
line = mtl_skip_ws(endptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool mtl_skip_required_tokens(const char *&line, int count)
|
||||
{
|
||||
for (int i = 0; i < count; ++i) {
|
||||
line = mtl_skip_ws(line);
|
||||
if (*line == 0)
|
||||
return false;
|
||||
line = mtl_skip_token(line);
|
||||
}
|
||||
line = mtl_skip_ws(line);
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::string mtl_parse_texture_name(const char *line)
|
||||
{
|
||||
const char *original = mtl_skip_ws(line);
|
||||
const char *current = original;
|
||||
|
||||
while (*current == '-') {
|
||||
const char *option_begin = current;
|
||||
const char *option_end = mtl_skip_token(current);
|
||||
current = option_end;
|
||||
|
||||
if (mtl_token_equals(option_begin, option_end, "-o") ||
|
||||
mtl_token_equals(option_begin, option_end, "-s") ||
|
||||
mtl_token_equals(option_begin, option_end, "-t")) {
|
||||
int skipped = 0;
|
||||
while (skipped < 3 && mtl_skip_numeric_token(current))
|
||||
++skipped;
|
||||
if (skipped == 0)
|
||||
return mtl_trim_value(original);
|
||||
continue;
|
||||
}
|
||||
|
||||
int option_args = -1;
|
||||
if (mtl_token_equals(option_begin, option_end, "-mm"))
|
||||
option_args = 2;
|
||||
else if (mtl_token_equals(option_begin, option_end, "-bm") ||
|
||||
mtl_token_equals(option_begin, option_end, "-boost") ||
|
||||
mtl_token_equals(option_begin, option_end, "-texres") ||
|
||||
mtl_token_equals(option_begin, option_end, "-clamp") ||
|
||||
mtl_token_equals(option_begin, option_end, "-blendu") ||
|
||||
mtl_token_equals(option_begin, option_end, "-blendv") ||
|
||||
mtl_token_equals(option_begin, option_end, "-cc") ||
|
||||
mtl_token_equals(option_begin, option_end, "-imfchan") ||
|
||||
mtl_token_equals(option_begin, option_end, "-type"))
|
||||
option_args = 1;
|
||||
|
||||
if (option_args < 0 || !mtl_skip_required_tokens(current, option_args))
|
||||
return mtl_trim_value(original);
|
||||
}
|
||||
|
||||
return mtl_trim_value(current);
|
||||
}
|
||||
|
||||
static bool mtl_parseline(const char *line, MtlData &data)
|
||||
{
|
||||
if (*line == 0) return true;
|
||||
@@ -394,13 +492,14 @@ static bool mtl_parseline(const char *line, MtlData &data)
|
||||
ObjNewMtl new_mtl;
|
||||
cur_mtl_name = line;
|
||||
data.new_mtl_unmap[cur_mtl_name] = std::make_shared<ObjNewMtl>();
|
||||
data.mtl_orders.emplace_back(cur_mtl_name);
|
||||
break;
|
||||
}
|
||||
case 'm': {
|
||||
if (*(line++) != 'a' || *(line++) != 'p' || *(line++) != '_' || *(line++) != 'K' || *(line++) != 'd') return false;
|
||||
EATWS();
|
||||
if (data.new_mtl_unmap.find(cur_mtl_name) != data.new_mtl_unmap.end()) {
|
||||
data.new_mtl_unmap[cur_mtl_name]->map_Kd = line;
|
||||
data.new_mtl_unmap[cur_mtl_name]->map_Kd = mtl_parse_texture_name(line);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -122,6 +122,9 @@ struct MtlData
|
||||
// Version of the data structure for load / store in the private binary format.
|
||||
int version;
|
||||
std::unordered_map<std::string, std::shared_ptr<ObjNewMtl>> new_mtl_unmap;
|
||||
// Material names in declaration order. new_mtl_unmap is unordered, but OBJ material
|
||||
// indices are positional, so texture import needs the original order.
|
||||
std::vector<std::string> mtl_orders;
|
||||
};
|
||||
extern bool objparse(const char *path, ObjData &data);
|
||||
extern bool mtlparse(const char *path, MtlData &data);
|
||||
|
||||
Reference in New Issue
Block a user