Files
OrcaSlicer/src/libslic3r/Format/OBJ.cpp
T
HanifKoh 4895bc03b4 Remove Unused Project Includes and Forward-Declare Where a Type Is Only Referenced (#16099)
* Remove Unused Project Includes and Forward-Declare Where a Type Is Only Referenced

Generated with include-what-you-use and applied conservatively. Only OrcaSlicer's own headers, the ones under src/ and tests/, are removed or forward-declared; standard-library and third-party includes are left alone. An include is removed only when both the Release and the Debug configuration leave it unused, never from inside a conditional block, and never from a file with platform-specific blocks, which only gain includes. Files whose only use of a header sits behind a feature or debug macro (libvgcode's OpenGL ES and marker code, the ARACHNE/TESTS_EXPORT_SVGS debug output) keep their includes.

clonable_ptr.hpp gains #pragma once; it had no include guard and was only safe while Config.hpp was its sole includer.

* Remove Unused Project Includes From Files With Platform-Specific Code

A Linux include-what-you-use run cannot see the code inside _WIN32, __APPLE__ or __linux__ blocks, so its verdict is only taken where nothing the removed header declares, directly or through what it includes, is named inside those blocks. Removals also have to hold in both the Release and Debug configuration and never touch a line inside a conditional block.

* Restore the libslic3r Precompiled Header and Direct Includes Lost in the Platform Pass

The platform-file pass treated pchheader.hpp as an ordinary header and
emptied it, and left GUI_Preview.hpp and 14 other files relying on
headers they no longer reached directly.

* Restore MainFrame.hpp in ParamsDialog.cpp for the Windows-Only Reparent Call

* Include Headers That Files Reached Through Ones the Cleanup Removed

* Drop Includes Duplicated by the Cleanup or by Main's Own Additions

* Leave PreciseSeam.cpp as Main Has It After the Precise Seam Rework
2026-10-05 16:47:17 +08:00

420 lines
17 KiB
C++

#include "../Model.hpp"
#include "../TriangleMesh.hpp"
#include "../TexturePainting.hpp"
#include "libslic3r/Color.hpp"
#include "libslic3r/Point.hpp"
#include "ResourcePathUtils.hpp"
#include "OBJ.hpp"
#include "objparser.hpp"
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>
#include <cstddef>
#include <algorithm>
#include <cassert>
#include <array>
#include <cstring>
#include <map>
#include <ios>
#include <string>
#include <boost/log/trivial.hpp>
#include <boost/nowide/fstream.hpp>
#include <utility>
#ifdef _WIN32
#define DIR_SEPARATOR '\\'
#else
#define DIR_SEPARATOR '/'
#endif
//Translation
#include "I18N.hpp"
#define _L(s) Slic3r::I18N::translate(s)
namespace Slic3r {
bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::string &message, ObjParser::MtlData *out_mtl)
{
if (meshptr == nullptr)
return false;
// Parse the OBJ file.
ObjParser::ObjData data;
ObjParser::MtlData mtl_data;
if (! ObjParser::objparse(path, data)) {
BOOST_LOG_TRIVIAL(error) << "load_obj: failed to parse " << path;
message = _L("load_obj: failed to parse");
return false;
}
bool exist_mtl = false;
if (data.mtllibs.size() > 0) { // read mtl
for (auto mtl_name : data.mtllibs) {
if (mtl_name.size() == 0){
continue;
}
exist_mtl = true;
bool mtl_name_is_path = false;
boost::filesystem::path mtl_abs_path(mtl_name);
if (boost::filesystem::exists(mtl_abs_path)) {
mtl_name_is_path = true;
}
boost::filesystem::path mtl_path;
if (!mtl_name_is_path) {
boost::filesystem::path full_path(path);
std::string dir = full_path.parent_path().string();
auto mtl_file = dir + "/" + mtl_name;
boost::filesystem::path temp_mtl_path(mtl_file);
mtl_path = temp_mtl_path;
}
const std::string _mtl_path = (mtl_name_is_path ? mtl_abs_path : mtl_path).string();
if (boost::filesystem::exists(mtl_name_is_path ? mtl_abs_path : mtl_path)) {
if (!ObjParser::mtlparse(_mtl_path.c_str(), mtl_data)) {
BOOST_LOG_TRIVIAL(error) << "load_obj:load_mtl: failed to parse " << _mtl_path;
message = _L("load mtl in obj: failed to parse");
return false;
}
}
else {
BOOST_LOG_TRIVIAL(error) << "load_obj: failed to load mtl_path:" << _mtl_path;
}
}
}
// Count the faces and verify, that all faces are triangular.
size_t num_faces = 0;
size_t num_quads = 0;
for (size_t i = 0; i < data.vertices.size(); ++ i) {
// Find the end of face.
size_t j = i;
for (; j < data.vertices.size() && data.vertices[j].coordIdx != -1; ++ j) ;
if (size_t num_face_vertices = j - i; num_face_vertices > 0) {
if (num_face_vertices > 4) {
// Non-triangular and non-quad faces are not supported as of now.
BOOST_LOG_TRIVIAL(error) << "load_obj: failed to parse " << path << ". The file contains polygons with more than 4 vertices.";
message = _L("The file contains polygons with more than 4 vertices.");
return false;
} else if (num_face_vertices < 3) {
// Non-triangular and non-quad faces are not supported as of now.
BOOST_LOG_TRIVIAL(error) << "load_obj: failed to parse " << path << ". The file contains polygons with less than 2 vertices.";
message = _L("The file contains polygons with less than 2 vertices.");
return false;
}
if (num_face_vertices == 4)
++ num_quads;
++ num_faces;
i = j;
}
}
// Convert ObjData into indexed triangle set.
indexed_triangle_set its;
size_t num_vertices = data.coordinates.size() / OBJ_VERTEX_LENGTH;
its.vertices.reserve(num_vertices);
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;
for (size_t i = 0; i < num_vertices; ++ i) {
size_t j = i * OBJ_VERTEX_LENGTH;
its.vertices.emplace_back(data.coordinates[j], data.coordinates[j + 1], data.coordinates[j + 2]);
if (data.has_vertex_color) {
RGBA color{std::clamp(data.coordinates[j + 3], 0.f, 1.f), std::clamp(data.coordinates[j + 4], 0.f, 1.f), std::clamp(data.coordinates[j + 5], 0.f, 1.f),
std::clamp(data.coordinates[j + 6], 0.f, 1.f)};
obj_info.vertex_colors.emplace_back(color);
}
}
int indices[ONE_FACE_SIZE];
int uvs[ONE_FACE_SIZE];
for (size_t i = 0; i < data.vertices.size();)
if (data.vertices[i].coordIdx == -1)
++ i;
else {
int cnt = 0;
while (i < data.vertices.size())
if (const ObjParser::ObjVertex &vertex = data.vertices[i ++]; vertex.coordIdx == -1) {
break;
} else {
assert(cnt < OBJ_VERTEX_LENGTH);
if (vertex.coordIdx < 0 || vertex.coordIdx >= int(its.vertices.size())) {
BOOST_LOG_TRIVIAL(error) << "load_obj: failed to parse " << path << ". The file contains invalid vertex index.";
message = _L("The file contains invalid vertex index.");
return false;
}
indices[cnt] = vertex.coordIdx;
uvs[cnt] = vertex.textureCoordIdx;
cnt++;
}
if (cnt) {
assert(cnt == 3 || cnt == 4);
// Insert one or two faces (triangulate a quad).
its.indices.emplace_back(indices[0], indices[1], indices[2]);
int face_index =its.indices.size() - 1;
RGBA face_color;
auto set_face_color = [&uvs, &data, &mtl_data, &obj_info, &face_color](int face_index, const std::string mtl_name, const std::array<int, 3> &corners) {
if (mtl_data.new_mtl_unmap.find(mtl_name) != mtl_data.new_mtl_unmap.end()) {
bool is_merge_ka_kd = true;
for (size_t n = 0; n < 3; n++) {
if (float(mtl_data.new_mtl_unmap[mtl_name]->Ka[n] + mtl_data.new_mtl_unmap[mtl_name]->Kd[n]) > 1.0) {
is_merge_ka_kd=false;
break;
}
}
for (size_t n = 0; n < 3; n++) {
if (is_merge_ka_kd) {
face_color[n] = std::clamp(float(mtl_data.new_mtl_unmap[mtl_name]->Ka[n] + mtl_data.new_mtl_unmap[mtl_name]->Kd[n]), 0.f, 1.f);
}
else {
face_color[n] = std::clamp(float(mtl_data.new_mtl_unmap[mtl_name]->Kd[n]), 0.f, 1.f);
}
}
face_color[3] = mtl_data.new_mtl_unmap[mtl_name]->Tr; // alpha
if (mtl_data.new_mtl_unmap[mtl_name]->map_Kd.size() > 0) {
auto png_name = mtl_data.new_mtl_unmap[mtl_name]->map_Kd;
obj_info.has_uv_png = true;
if (obj_info.pngs.find(png_name) == obj_info.pngs.end()) { obj_info.pngs[png_name] = false; }
obj_info.uv_map_pngs[face_index] = png_name;
}
if (data.textureCoordinates.size() > 0) {
// A face vertex may omit vt or reference a missing one. Fall back to (0, 0) rather than
// skipping the face, so obj_info.uvs stays aligned with the face indices.
const int uv_count = static_cast<int>(data.textureCoordinates.size() / OBJ_TEXCOORD_LENGTH);
auto uv_at = [&data, uv_count](int idx) -> Vec2f {
if (idx < 0 || idx >= uv_count)
return Vec2f::Zero();
return Vec2f(data.textureCoordinates[idx * OBJ_TEXCOORD_LENGTH], data.textureCoordinates[idx * OBJ_TEXCOORD_LENGTH + 1]);
};
std::array<Vec2f, 3> uv_array{uv_at(uvs[corners[0]]), uv_at(uvs[corners[1]]), uv_at(uvs[corners[2]])};
obj_info.uvs.emplace_back(uv_array);
}
obj_info.face_colors.emplace_back(face_color);
}
else {
if (obj_info.lost_material_name.empty()) {
obj_info.lost_material_name = mtl_name;
}
}
};
auto set_face_color_by_mtl = [&data, &set_face_color](int face_index, const std::array<int, 3> &corners) {
if (data.usemtls.size() == 1) {
set_face_color(face_index, data.usemtls[0].name, corners);
} else {
for (size_t k = 0; k < data.usemtls.size(); k++) {
auto mtl = data.usemtls[k];
if (face_index >= mtl.face_start && face_index <= mtl.face_end) {
set_face_color(face_index, data.usemtls[k].name, corners);
break;
}
}
}
};
if (exist_mtl) {
set_face_color_by_mtl(face_index, {0, 1, 2});
}
if (cnt == 4) {
its.indices.emplace_back(indices[0], indices[2], indices[3]);
int face_index = its.indices.size() - 1;
if (exist_mtl) {
set_face_color_by_mtl(face_index, {0, 2, 3});
}
}
}
}
*meshptr = TriangleMesh(std::move(its));
if (meshptr->empty()) {
BOOST_LOG_TRIVIAL(error) << "load_obj: This OBJ file couldn't be read because it's empty. " << path;
message = _L("This OBJ file couldn't be read because it's empty.");
return false;
}
if (meshptr->volume() < 0) {
meshptr->flip_triangles();
// Flipping swaps corners 1 and 2 of every face, so the UVs have to follow.
for (std::array<Vec2f, 3> &uv : obj_info.uvs)
std::swap(uv[1], uv[2]);
}
// 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, ObjParser::MtlData *out_mtl)
{
TriangleMesh mesh;
bool ret = load_obj(path, &mesh, obj_info, message, out_mtl);
if (ret) {
std::string object_name;
if (object_name_in == nullptr) {
const char *last_slash = strrchr(path, DIR_SEPARATOR);
object_name.assign((last_slash == nullptr) ? path : last_slash + 1);
} else
object_name.assign(object_name_in);
model->add_object(object_name.c_str(), path, std::move(mesh));
}
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.
mesh->WriteOBJFile(path);
return true;
}
bool store_obj(const char *path, ModelObject *model_object)
{
TriangleMesh mesh = model_object->mesh();
return store_obj(path, &mesh);
}
bool store_obj(const char *path, Model *model)
{
TriangleMesh mesh = model->mesh();
return store_obj(path, &mesh);
}
}; // namespace Slic3r