Add Chromium-only Blender WebEngine parity work
This commit is contained in:
78
blender-5.2.0/source/blender/io/ply/CMakeLists.txt
Normal file
78
blender-5.2.0/source/blender/io/ply/CMakeLists.txt
Normal file
@@ -0,0 +1,78 @@
|
||||
# SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
set(INC
|
||||
.
|
||||
exporter
|
||||
importer
|
||||
intern
|
||||
../common
|
||||
../../editors/include
|
||||
../../makesrna
|
||||
)
|
||||
|
||||
set(INC_SYS
|
||||
)
|
||||
|
||||
set(SRC
|
||||
exporter/ply_export.cc
|
||||
exporter/ply_export_data.cc
|
||||
exporter/ply_export_header.cc
|
||||
exporter/ply_export_load_plydata.cc
|
||||
exporter/ply_file_buffer.cc
|
||||
exporter/ply_file_buffer_ascii.cc
|
||||
exporter/ply_file_buffer_binary.cc
|
||||
importer/ply_import.cc
|
||||
importer/ply_import_buffer.cc
|
||||
importer/ply_import_data.cc
|
||||
importer/ply_import_mesh.cc
|
||||
IO_ply.cc
|
||||
|
||||
exporter/ply_export.hh
|
||||
exporter/ply_export_data.hh
|
||||
exporter/ply_export_header.hh
|
||||
exporter/ply_export_load_plydata.hh
|
||||
exporter/ply_file_buffer.hh
|
||||
exporter/ply_file_buffer_ascii.hh
|
||||
exporter/ply_file_buffer_binary.hh
|
||||
importer/ply_import.hh
|
||||
importer/ply_import_buffer.hh
|
||||
importer/ply_import_data.hh
|
||||
importer/ply_import_mesh.hh
|
||||
IO_ply.hh
|
||||
|
||||
intern/ply_data.hh
|
||||
)
|
||||
|
||||
set(LIB
|
||||
PRIVATE bf::blenkernel
|
||||
PRIVATE bf::blenlib
|
||||
PRIVATE bf::bmesh
|
||||
PRIVATE bf::depsgraph
|
||||
PRIVATE bf::dna
|
||||
PRIVATE bf::geometry
|
||||
PRIVATE bf::intern::clog
|
||||
PRIVATE bf::intern::guardedalloc
|
||||
bf_io_common
|
||||
PRIVATE bf::extern::fast_float
|
||||
PRIVATE bf::windowmanager
|
||||
)
|
||||
|
||||
blender_add_lib(bf_io_ply "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
|
||||
|
||||
if(WITH_GTESTS)
|
||||
set(TEST_SRC
|
||||
tests/io_ply_exporter_test.cc
|
||||
tests/io_ply_importer_test.cc
|
||||
)
|
||||
set(TEST_INC
|
||||
../../blenloader
|
||||
../../../../tests/gtests
|
||||
)
|
||||
set(TEST_LIB
|
||||
bf_io_ply
|
||||
bf_blenloader_test_util
|
||||
)
|
||||
blender_add_test_suite_lib(io_ply "${TEST_SRC}" "${INC};${TEST_INC}" "${INC_SYS}" "${LIB};${TEST_LIB}")
|
||||
endif()
|
||||
51
blender-5.2.0/source/blender/io/ply/IO_ply.cc
Normal file
51
blender-5.2.0/source/blender/io/ply/IO_ply.cc
Normal file
@@ -0,0 +1,51 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include "BLI_timeit.hh"
|
||||
|
||||
#include "DNA_windowmanager_types.h"
|
||||
|
||||
#include "IO_ply.hh"
|
||||
|
||||
#include "ply_export.hh"
|
||||
#include "ply_import.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
using namespace blender::timeit;
|
||||
|
||||
static void report_duration(const char *job, const TimePoint &start_time, const char *path)
|
||||
{
|
||||
Nanoseconds duration = Clock::now() - start_time;
|
||||
fmt::print("PLY {} of '{}' took ", job, BLI_path_basename(path));
|
||||
print_duration(duration);
|
||||
fmt::print("\n");
|
||||
}
|
||||
|
||||
void PLY_export(bContext *C, const PLYExportParams ¶ms)
|
||||
{
|
||||
TimePoint start_time = Clock::now();
|
||||
io::ply::exporter_main(C, params);
|
||||
report_duration("export", start_time, params.filepath);
|
||||
}
|
||||
|
||||
void PLY_import(bContext *C, const PLYImportParams ¶ms)
|
||||
{
|
||||
TimePoint start_time = Clock::now();
|
||||
io::ply::importer_main(C, params);
|
||||
report_duration("import", start_time, params.filepath);
|
||||
}
|
||||
|
||||
Mesh *PLY_import_mesh(const PLYImportParams ¶ms)
|
||||
{
|
||||
return io::ply::import_mesh(params);
|
||||
}
|
||||
|
||||
} // namespace blender
|
||||
79
blender-5.2.0/source/blender/io/ply/IO_ply.hh
Normal file
79
blender-5.2.0/source/blender/io/ply/IO_ply.hh
Normal file
@@ -0,0 +1,79 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_path_utils.hh"
|
||||
|
||||
#include "DNA_ID.h"
|
||||
|
||||
#include "IO_orientation.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Mesh;
|
||||
struct bContext;
|
||||
struct ReportList;
|
||||
|
||||
enum class ePLYVertexColorMode {
|
||||
None = 0,
|
||||
sRGB = 1,
|
||||
Linear = 2,
|
||||
};
|
||||
|
||||
struct PLYExportParams {
|
||||
/** Full path to the destination `.PLY` file. */
|
||||
char filepath[FILE_MAX] = "";
|
||||
/** Pretend that destination file folder is this, if non-empty. Used only for tests. */
|
||||
char file_base_for_tests[FILE_MAX] = "";
|
||||
|
||||
/** Full path to current blender file (used for comments in output). */
|
||||
const char *blen_filepath = nullptr;
|
||||
|
||||
/** File export format, ASCII if true, binary otherwise. */
|
||||
bool ascii_format = false;
|
||||
|
||||
/* Geometry Transform options. */
|
||||
eIOAxis forward_axis = IO_AXIS_Y;
|
||||
eIOAxis up_axis = IO_AXIS_Z;
|
||||
float global_scale = 1.0f;
|
||||
|
||||
/* File Write Options. */
|
||||
bool export_selected_objects = false;
|
||||
bool apply_modifiers = true;
|
||||
bool export_uv = true;
|
||||
bool export_normals = false;
|
||||
ePLYVertexColorMode vertex_colors = ePLYVertexColorMode::sRGB;
|
||||
bool export_attributes = true;
|
||||
bool export_triangulated_mesh = false;
|
||||
char collection[MAX_ID_NAME - 2] = "";
|
||||
|
||||
ReportList *reports = nullptr;
|
||||
};
|
||||
|
||||
struct PLYImportParams {
|
||||
/** Full path to the source PLY file to import. */
|
||||
char filepath[FILE_MAX] = "";
|
||||
eIOAxis forward_axis = IO_AXIS_Y;
|
||||
eIOAxis up_axis = IO_AXIS_Z;
|
||||
bool use_scene_unit = false;
|
||||
float global_scale = 1.0f;
|
||||
ePLYVertexColorMode vertex_colors = ePLYVertexColorMode::sRGB;
|
||||
bool import_attributes = true;
|
||||
bool merge_verts = false;
|
||||
|
||||
ReportList *reports = nullptr;
|
||||
};
|
||||
|
||||
void PLY_export(bContext *C, const PLYExportParams ¶ms);
|
||||
|
||||
void PLY_import(bContext *C, const PLYImportParams ¶ms);
|
||||
|
||||
Mesh *PLY_import_mesh(const PLYImportParams ¶ms);
|
||||
|
||||
} // namespace blender
|
||||
102
blender-5.2.0/source/blender/io/ply/exporter/ply_export.cc
Normal file
102
blender-5.2.0/source/blender/io/ply/exporter/ply_export.cc
Normal file
@@ -0,0 +1,102 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#include "BKE_context.hh"
|
||||
#include "BKE_lib_id.hh"
|
||||
#include "BKE_report.hh"
|
||||
#include "BKE_scene.hh"
|
||||
|
||||
#include "DEG_depsgraph_query.hh"
|
||||
|
||||
#include "ED_util.hh"
|
||||
|
||||
#include "IO_ply.hh"
|
||||
|
||||
#include "ply_data.hh"
|
||||
#include "ply_export.hh"
|
||||
#include "ply_export_data.hh"
|
||||
#include "ply_export_header.hh"
|
||||
#include "ply_export_load_plydata.hh"
|
||||
#include "ply_file_buffer_ascii.hh"
|
||||
#include "ply_file_buffer_binary.hh"
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
static CLG_LogRef LOG = {"io.ply"};
|
||||
|
||||
namespace io::ply {
|
||||
|
||||
void exporter_main(bContext *C, const PLYExportParams &export_params)
|
||||
{
|
||||
std::unique_ptr<io::ply::PlyData> plyData = std::make_unique<PlyData>();
|
||||
|
||||
Main *bmain = CTX_data_main(C);
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
ViewLayer *view_layer = CTX_data_view_layer(C);
|
||||
|
||||
ED_editors_flush_edits(bmain);
|
||||
|
||||
Depsgraph *depsgraph = DEG_graph_new(bmain, scene, view_layer, DAG_EVAL_RENDER);
|
||||
|
||||
if (export_params.collection[0]) {
|
||||
Collection *collection = reinterpret_cast<Collection *>(
|
||||
BKE_libblock_find_name(bmain, ID_GR, export_params.collection));
|
||||
if (!collection) {
|
||||
BKE_reportf(export_params.reports,
|
||||
RPT_ERROR,
|
||||
"PLY Export: Unable to find collection '%s'",
|
||||
export_params.collection);
|
||||
|
||||
DEG_graph_free(depsgraph);
|
||||
return;
|
||||
}
|
||||
|
||||
DEG_graph_build_from_collection(depsgraph, collection);
|
||||
}
|
||||
else {
|
||||
DEG_graph_build_from_view_layer(depsgraph);
|
||||
}
|
||||
BKE_scene_graph_update_tagged(depsgraph, bmain);
|
||||
|
||||
load_plydata(*plyData, depsgraph, export_params);
|
||||
|
||||
DEG_graph_free(depsgraph);
|
||||
|
||||
std::unique_ptr<FileBuffer> buffer;
|
||||
|
||||
try {
|
||||
if (export_params.ascii_format) {
|
||||
buffer = std::make_unique<FileBufferAscii>(export_params.filepath);
|
||||
}
|
||||
else {
|
||||
buffer = std::make_unique<FileBufferBinary>(export_params.filepath);
|
||||
}
|
||||
}
|
||||
catch (const std::system_error &ex) {
|
||||
CLOG_ERROR(&LOG, "[%s] %s", ex.code().category().name(), ex.what());
|
||||
BKE_reportf(export_params.reports,
|
||||
RPT_ERROR,
|
||||
"PLY Export: Cannot open file '%s'",
|
||||
export_params.filepath);
|
||||
return;
|
||||
}
|
||||
|
||||
write_header(*buffer, *plyData, export_params);
|
||||
|
||||
write_vertices(*buffer, *plyData);
|
||||
|
||||
write_faces(*buffer, *plyData);
|
||||
|
||||
write_edges(*buffer, *plyData);
|
||||
|
||||
buffer->close_file();
|
||||
}
|
||||
} // namespace io::ply
|
||||
} // namespace blender
|
||||
22
blender-5.2.0/source/blender/io/ply/exporter/ply_export.hh
Normal file
22
blender-5.2.0/source/blender/io/ply/exporter/ply_export.hh
Normal file
@@ -0,0 +1,22 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct bContext;
|
||||
struct PLYExportParams;
|
||||
|
||||
namespace io::ply {
|
||||
|
||||
/* Main export function used from within Blender. */
|
||||
void exporter_main(bContext *C, const PLYExportParams &export_params);
|
||||
|
||||
} // namespace io::ply
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,63 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#include "ply_export_data.hh"
|
||||
#include "ply_data.hh"
|
||||
#include "ply_file_buffer.hh"
|
||||
|
||||
#include "BLI_math_vector.hh"
|
||||
|
||||
namespace blender::io::ply {
|
||||
|
||||
void write_vertices(FileBuffer &buffer, const PlyData &ply_data)
|
||||
{
|
||||
for (int i = 0; i < ply_data.vertices.size(); i++) {
|
||||
buffer.write_vertex(ply_data.vertices[i].x, ply_data.vertices[i].y, ply_data.vertices[i].z);
|
||||
|
||||
if (!ply_data.vertex_normals.is_empty()) {
|
||||
buffer.write_vertex_normal(ply_data.vertex_normals[i].x,
|
||||
ply_data.vertex_normals[i].y,
|
||||
ply_data.vertex_normals[i].z);
|
||||
}
|
||||
|
||||
if (!ply_data.vertex_colors.is_empty()) {
|
||||
/* PLY colors currently are exported as bytes, make sure inputs are clamped. */
|
||||
float4 color = math::clamp(ply_data.vertex_colors[i], 0.0f, 1.0f) * 255.0f;
|
||||
buffer.write_vertex_color(uchar(color.x), uchar(color.y), uchar(color.z), uchar(color.w));
|
||||
}
|
||||
|
||||
if (!ply_data.uv_coordinates.is_empty()) {
|
||||
buffer.write_UV(ply_data.uv_coordinates[i].x, ply_data.uv_coordinates[i].y);
|
||||
}
|
||||
|
||||
for (const PlyCustomAttribute &attr : ply_data.vertex_custom_attr) {
|
||||
buffer.write_data(attr.data[i]);
|
||||
}
|
||||
|
||||
buffer.write_vertex_end();
|
||||
}
|
||||
buffer.write_to_file();
|
||||
}
|
||||
|
||||
void write_faces(FileBuffer &buffer, const PlyData &ply_data)
|
||||
{
|
||||
const uint32_t *indices = ply_data.face_vertices.data();
|
||||
for (uint32_t face_size : ply_data.face_sizes) {
|
||||
buffer.write_face(char(face_size), Span<uint32_t>(indices, face_size));
|
||||
indices += face_size;
|
||||
}
|
||||
buffer.write_to_file();
|
||||
}
|
||||
void write_edges(FileBuffer &buffer, const PlyData &ply_data)
|
||||
{
|
||||
for (const std::pair<int, int> &edge : ply_data.edges) {
|
||||
buffer.write_edge(edge.first, edge.second);
|
||||
}
|
||||
buffer.write_to_file();
|
||||
}
|
||||
} // namespace blender::io::ply
|
||||
@@ -0,0 +1,22 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace blender::io::ply {
|
||||
|
||||
class FileBuffer;
|
||||
struct PlyData;
|
||||
|
||||
void write_vertices(FileBuffer &buffer, const PlyData &ply_data);
|
||||
|
||||
void write_faces(FileBuffer &buffer, const PlyData &ply_data);
|
||||
|
||||
void write_edges(FileBuffer &buffer, const PlyData &ply_data);
|
||||
|
||||
} // namespace blender::io::ply
|
||||
@@ -0,0 +1,72 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#include "BKE_blender_version.h"
|
||||
|
||||
#include "IO_ply.hh"
|
||||
#include "ply_data.hh"
|
||||
#include "ply_export_header.hh"
|
||||
#include "ply_file_buffer.hh"
|
||||
|
||||
namespace blender::io::ply {
|
||||
|
||||
void write_header(FileBuffer &buffer,
|
||||
const PlyData &ply_data,
|
||||
const PLYExportParams &export_params)
|
||||
{
|
||||
buffer.write_string("ply");
|
||||
|
||||
StringRef format = export_params.ascii_format ? "ascii" : "binary_little_endian";
|
||||
buffer.write_string("format " + format + " 1.0");
|
||||
|
||||
StringRef version = BKE_blender_version_string();
|
||||
buffer.write_string("comment Created in Blender version " + version);
|
||||
|
||||
buffer.write_header_element("vertex", int32_t(ply_data.vertices.size()));
|
||||
buffer.write_header_scalar_property("float", "x");
|
||||
buffer.write_header_scalar_property("float", "y");
|
||||
buffer.write_header_scalar_property("float", "z");
|
||||
|
||||
if (!ply_data.vertex_normals.is_empty()) {
|
||||
buffer.write_header_scalar_property("float", "nx");
|
||||
buffer.write_header_scalar_property("float", "ny");
|
||||
buffer.write_header_scalar_property("float", "nz");
|
||||
}
|
||||
|
||||
if (!ply_data.vertex_colors.is_empty()) {
|
||||
buffer.write_header_scalar_property("uchar", "red");
|
||||
buffer.write_header_scalar_property("uchar", "green");
|
||||
buffer.write_header_scalar_property("uchar", "blue");
|
||||
buffer.write_header_scalar_property("uchar", "alpha");
|
||||
}
|
||||
|
||||
if (!ply_data.uv_coordinates.is_empty()) {
|
||||
buffer.write_header_scalar_property("float", "s");
|
||||
buffer.write_header_scalar_property("float", "t");
|
||||
}
|
||||
|
||||
for (const PlyCustomAttribute &attr : ply_data.vertex_custom_attr) {
|
||||
buffer.write_header_scalar_property("float", attr.name);
|
||||
}
|
||||
|
||||
if (!ply_data.face_sizes.is_empty()) {
|
||||
buffer.write_header_element("face", int(ply_data.face_sizes.size()));
|
||||
buffer.write_header_list_property("uchar", "uint", "vertex_indices");
|
||||
}
|
||||
|
||||
if (!ply_data.edges.is_empty()) {
|
||||
buffer.write_header_element("edge", int(ply_data.edges.size()));
|
||||
buffer.write_header_scalar_property("int", "vertex1");
|
||||
buffer.write_header_scalar_property("int", "vertex2");
|
||||
}
|
||||
|
||||
buffer.write_string("end_header");
|
||||
buffer.write_to_file();
|
||||
}
|
||||
|
||||
} // namespace blender::io::ply
|
||||
@@ -0,0 +1,22 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
struct PLYExportParams;
|
||||
|
||||
namespace blender::io::ply {
|
||||
|
||||
class FileBuffer;
|
||||
struct PlyData;
|
||||
|
||||
void write_header(FileBuffer &buffer,
|
||||
const PlyData &ply_data,
|
||||
const PLYExportParams &export_params);
|
||||
|
||||
} // namespace blender::io::ply
|
||||
@@ -0,0 +1,483 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#include "ply_export_load_plydata.hh"
|
||||
#include "IO_mesh_utils.hh"
|
||||
#include "IO_ply.hh"
|
||||
#include "ply_data.hh"
|
||||
|
||||
#include "BKE_anonymous_attribute_id.hh"
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_lib_id.hh"
|
||||
#include "BKE_mesh.hh"
|
||||
#include "BKE_mesh_wrapper.hh"
|
||||
#include "BLI_array_utils.hh"
|
||||
#include "BLI_color.hh"
|
||||
#include "BLI_hash.hh"
|
||||
#include "BLI_math_matrix.h"
|
||||
#include "BLI_math_quaternion_types.hh"
|
||||
#include "BLI_math_rotation.h"
|
||||
#include "BLI_math_vector.h"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "DEG_depsgraph_query.hh"
|
||||
|
||||
#include "DNA_customdata_types.h"
|
||||
#include "DNA_layer_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
#include "bmesh.hh"
|
||||
#include "tools/bmesh_triangulate.hh"
|
||||
|
||||
namespace blender::io::ply {
|
||||
|
||||
static Mesh *do_triangulation(const Mesh *mesh, bool force_triangulation)
|
||||
{
|
||||
const BMeshCreateParams bm_create_params = {false};
|
||||
BMeshFromMeshParams bm_convert_params{};
|
||||
bm_convert_params.calc_face_normal = true;
|
||||
bm_convert_params.calc_vert_normal = true;
|
||||
const int triangulation_threshold = force_triangulation ? 4 : 255;
|
||||
|
||||
BMesh *bmesh = BKE_mesh_to_bmesh_ex(mesh, &bm_create_params, &bm_convert_params);
|
||||
BM_mesh_triangulate(bmesh, 0, 3, triangulation_threshold, false, nullptr, nullptr, nullptr);
|
||||
Mesh *temp_mesh = BKE_mesh_from_bmesh_for_eval_nomain(bmesh, nullptr, mesh);
|
||||
BM_mesh_free(bmesh);
|
||||
return temp_mesh;
|
||||
}
|
||||
|
||||
static void set_world_axes_transform(const Object &object,
|
||||
const eIOAxis forward,
|
||||
const eIOAxis up,
|
||||
float r_world_and_axes_transform[4][4],
|
||||
float r_world_and_axes_normal_transform[3][3])
|
||||
{
|
||||
float axes_transform[3][3];
|
||||
unit_m3(axes_transform);
|
||||
/* +Y-forward and +Z-up are the default Blender axis settings. */
|
||||
mat3_from_axis_conversion(forward, up, IO_AXIS_Y, IO_AXIS_Z, axes_transform);
|
||||
mul_m4_m3m4(r_world_and_axes_transform, axes_transform, object.object_to_world().ptr());
|
||||
/* mul_m4_m3m4 does not transform last row of obmat, i.e. location data. */
|
||||
mul_v3_m3v3(r_world_and_axes_transform[3], axes_transform, object.object_to_world().location());
|
||||
r_world_and_axes_transform[3][3] = object.object_to_world()[3][3];
|
||||
|
||||
/* Normals need inverse transpose of the regular matrix to handle non-uniform scale. */
|
||||
float normal_matrix[3][3];
|
||||
copy_m3_m4(normal_matrix, r_world_and_axes_transform);
|
||||
invert_m3_m3(r_world_and_axes_normal_transform, normal_matrix);
|
||||
transpose_m3(r_world_and_axes_normal_transform);
|
||||
}
|
||||
|
||||
struct uv_vertex_key {
|
||||
float2 uv;
|
||||
int vertex_index;
|
||||
|
||||
bool operator==(const uv_vertex_key &r) const
|
||||
{
|
||||
return (uv == r.uv && vertex_index == r.vertex_index);
|
||||
}
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
return get_default_hash(uv.x, uv.y, vertex_index);
|
||||
}
|
||||
};
|
||||
|
||||
static void generate_vertex_map(const Mesh *mesh,
|
||||
const PLYExportParams &export_params,
|
||||
Vector<int> &r_ply_to_vertex,
|
||||
Vector<int> &r_vertex_to_ply,
|
||||
Vector<int> &r_loop_to_ply,
|
||||
Vector<float2> &r_uvs)
|
||||
{
|
||||
bool export_uv = false;
|
||||
VArraySpan<float2> uv_map;
|
||||
if (export_params.export_uv) {
|
||||
const StringRef uv_name = mesh->active_uv_map_name();
|
||||
if (!uv_name.is_empty()) {
|
||||
const bke::AttributeAccessor attributes = mesh->attributes();
|
||||
uv_map = *attributes.lookup<float2>(uv_name, bke::AttrDomain::Corner);
|
||||
export_uv = !uv_map.is_empty();
|
||||
}
|
||||
}
|
||||
|
||||
const Span<int> corner_verts = mesh->corner_verts();
|
||||
r_vertex_to_ply.resize(mesh->verts_num, -1);
|
||||
r_loop_to_ply.resize(mesh->corners_num, -1);
|
||||
|
||||
/* If we do not export or have UVs, then mapping of vertex indices is simple. */
|
||||
if (!export_uv) {
|
||||
r_ply_to_vertex.resize(mesh->verts_num);
|
||||
array_utils::fill_index_range(r_vertex_to_ply.as_mutable_span());
|
||||
array_utils::fill_index_range(r_ply_to_vertex.as_mutable_span());
|
||||
for (int index = 0; index < mesh->corners_num; index++) {
|
||||
r_loop_to_ply[index] = corner_verts[index];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* We are exporting UVs. Need to build mappings of what
|
||||
* any unique (vertex, UV) values will map into the PLY data. */
|
||||
Map<uv_vertex_key, int> vertex_map;
|
||||
vertex_map.reserve(mesh->verts_num);
|
||||
r_ply_to_vertex.reserve(mesh->verts_num);
|
||||
r_uvs.reserve(mesh->verts_num);
|
||||
|
||||
for (int loop_index = 0; loop_index < int(corner_verts.size()); loop_index++) {
|
||||
int vertex_index = corner_verts[loop_index];
|
||||
uv_vertex_key key{uv_map[loop_index], vertex_index};
|
||||
int ply_index = vertex_map.lookup_or_add(key, int(vertex_map.size()));
|
||||
r_vertex_to_ply[vertex_index] = ply_index;
|
||||
r_loop_to_ply[loop_index] = ply_index;
|
||||
while (r_uvs.size() <= ply_index) {
|
||||
r_uvs.append(key.uv);
|
||||
r_ply_to_vertex.append(key.vertex_index);
|
||||
}
|
||||
}
|
||||
|
||||
/* Add zero UVs for any loose vertices. */
|
||||
for (int vertex_index = 0; vertex_index < mesh->verts_num; vertex_index++) {
|
||||
if (r_vertex_to_ply[vertex_index] != -1) {
|
||||
continue;
|
||||
}
|
||||
int ply_index = int(r_uvs.size());
|
||||
r_vertex_to_ply[vertex_index] = ply_index;
|
||||
r_uvs.append({0, 0});
|
||||
r_ply_to_vertex.append(vertex_index);
|
||||
}
|
||||
}
|
||||
|
||||
static float *find_or_add_attribute(const StringRef name,
|
||||
int64_t size,
|
||||
uint32_t vertex_offset,
|
||||
Vector<PlyCustomAttribute> &r_attributes)
|
||||
{
|
||||
/* Do we have this attribute from some other object already? */
|
||||
for (PlyCustomAttribute &attr : r_attributes) {
|
||||
if (attr.name == name) {
|
||||
BLI_assert(attr.data.size() == vertex_offset);
|
||||
attr.data.resize(attr.data.size() + size, 0.0f);
|
||||
return attr.data.data() + vertex_offset;
|
||||
}
|
||||
}
|
||||
/* We don't have it yet, create and fill with zero data for previous objects. */
|
||||
r_attributes.append(PlyCustomAttribute(name, vertex_offset + size));
|
||||
return r_attributes.last().data.data() + vertex_offset;
|
||||
}
|
||||
|
||||
static void load_custom_attributes(const Mesh *mesh,
|
||||
const Span<int> ply_to_vertex,
|
||||
uint32_t vertex_offset,
|
||||
Vector<PlyCustomAttribute> &r_attributes)
|
||||
{
|
||||
const bke::AttributeAccessor attributes = mesh->attributes();
|
||||
const StringRef color_name = mesh->active_color_attribute;
|
||||
const StringRef uv_name = mesh->active_uv_map_name();
|
||||
const int64_t size = ply_to_vertex.size();
|
||||
|
||||
attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
|
||||
/* Skip internal, standard and non-vertex domain attributes. */
|
||||
if (iter.domain != bke::AttrDomain::Point || iter.name[0] == '.' ||
|
||||
bke::attribute_name_is_anonymous(iter.name) ||
|
||||
ELEM(iter.name, "position", color_name, uv_name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const GVArraySpan attribute = *iter.get();
|
||||
if (attribute.is_empty()) {
|
||||
return;
|
||||
}
|
||||
switch (iter.data_type) {
|
||||
case bke::AttrType::Float: {
|
||||
float *attr = find_or_add_attribute(iter.name, size, vertex_offset, r_attributes);
|
||||
auto typed = attribute.typed<float>();
|
||||
for (const int64_t i : ply_to_vertex.index_range()) {
|
||||
attr[i] = typed[ply_to_vertex[i]];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case bke::AttrType::Int8: {
|
||||
float *attr = find_or_add_attribute(iter.name, size, vertex_offset, r_attributes);
|
||||
auto typed = attribute.typed<int8_t>();
|
||||
for (const int64_t i : ply_to_vertex.index_range()) {
|
||||
attr[i] = typed[ply_to_vertex[i]];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case bke::AttrType::Int32: {
|
||||
float *attr = find_or_add_attribute(iter.name, size, vertex_offset, r_attributes);
|
||||
auto typed = attribute.typed<int32_t>();
|
||||
for (const int64_t i : ply_to_vertex.index_range()) {
|
||||
attr[i] = typed[ply_to_vertex[i]];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case bke::AttrType::Int16_2D: {
|
||||
float *attr_x = find_or_add_attribute(iter.name + "_x", size, vertex_offset, r_attributes);
|
||||
float *attr_y = find_or_add_attribute(iter.name + "_y", size, vertex_offset, r_attributes);
|
||||
auto typed = attribute.typed<short2>();
|
||||
for (const int64_t i : ply_to_vertex.index_range()) {
|
||||
int j = ply_to_vertex[i];
|
||||
attr_x[i] = typed[j].x;
|
||||
attr_y[i] = typed[j].y;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case bke::AttrType::Int32_2D: {
|
||||
float *attr_x = find_or_add_attribute(iter.name + "_x", size, vertex_offset, r_attributes);
|
||||
float *attr_y = find_or_add_attribute(iter.name + "_y", size, vertex_offset, r_attributes);
|
||||
auto typed = attribute.typed<int2>();
|
||||
for (const int64_t i : ply_to_vertex.index_range()) {
|
||||
int j = ply_to_vertex[i];
|
||||
attr_x[i] = typed[j].x;
|
||||
attr_y[i] = typed[j].y;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case bke::AttrType::Float2: {
|
||||
float *attr_x = find_or_add_attribute(iter.name + "_x", size, vertex_offset, r_attributes);
|
||||
float *attr_y = find_or_add_attribute(iter.name + "_y", size, vertex_offset, r_attributes);
|
||||
auto typed = attribute.typed<float2>();
|
||||
for (const int64_t i : ply_to_vertex.index_range()) {
|
||||
int j = ply_to_vertex[i];
|
||||
attr_x[i] = typed[j].x;
|
||||
attr_y[i] = typed[j].y;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case bke::AttrType::Float3: {
|
||||
float *attr_x = find_or_add_attribute(iter.name + "_x", size, vertex_offset, r_attributes);
|
||||
float *attr_y = find_or_add_attribute(iter.name + "_y", size, vertex_offset, r_attributes);
|
||||
float *attr_z = find_or_add_attribute(iter.name + "_z", size, vertex_offset, r_attributes);
|
||||
auto typed = attribute.typed<float3>();
|
||||
for (const int64_t i : ply_to_vertex.index_range()) {
|
||||
int j = ply_to_vertex[i];
|
||||
attr_x[i] = typed[j].x;
|
||||
attr_y[i] = typed[j].y;
|
||||
attr_z[i] = typed[j].z;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case bke::AttrType::ColorByte: {
|
||||
float *attr_r = find_or_add_attribute(iter.name + "_r", size, vertex_offset, r_attributes);
|
||||
float *attr_g = find_or_add_attribute(iter.name + "_g", size, vertex_offset, r_attributes);
|
||||
float *attr_b = find_or_add_attribute(iter.name + "_b", size, vertex_offset, r_attributes);
|
||||
float *attr_a = find_or_add_attribute(iter.name + "_a", size, vertex_offset, r_attributes);
|
||||
auto typed = attribute.typed<ColorGeometry4b>();
|
||||
for (const int64_t i : ply_to_vertex.index_range()) {
|
||||
ColorGeometry4f col = color::decode(typed[ply_to_vertex[i]]);
|
||||
attr_r[i] = col.r;
|
||||
attr_g[i] = col.g;
|
||||
attr_b[i] = col.b;
|
||||
attr_a[i] = col.a;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case bke::AttrType::ColorFloat: {
|
||||
float *attr_r = find_or_add_attribute(iter.name + "_r", size, vertex_offset, r_attributes);
|
||||
float *attr_g = find_or_add_attribute(iter.name + "_g", size, vertex_offset, r_attributes);
|
||||
float *attr_b = find_or_add_attribute(iter.name + "_b", size, vertex_offset, r_attributes);
|
||||
float *attr_a = find_or_add_attribute(iter.name + "_a", size, vertex_offset, r_attributes);
|
||||
auto typed = attribute.typed<ColorGeometry4f>();
|
||||
for (const int64_t i : ply_to_vertex.index_range()) {
|
||||
ColorGeometry4f col = typed[ply_to_vertex[i]];
|
||||
attr_r[i] = col.r;
|
||||
attr_g[i] = col.g;
|
||||
attr_b[i] = col.b;
|
||||
attr_a[i] = col.a;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case bke::AttrType::Bool: {
|
||||
float *attr = find_or_add_attribute(iter.name, size, vertex_offset, r_attributes);
|
||||
auto typed = attribute.typed<bool>();
|
||||
for (const int64_t i : ply_to_vertex.index_range()) {
|
||||
attr[i] = typed[ply_to_vertex[i]] ? 1.0f : 0.0f;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case bke::AttrType::Quaternion: {
|
||||
float *attr_x = find_or_add_attribute(iter.name + "_x", size, vertex_offset, r_attributes);
|
||||
float *attr_y = find_or_add_attribute(iter.name + "_y", size, vertex_offset, r_attributes);
|
||||
float *attr_z = find_or_add_attribute(iter.name + "_z", size, vertex_offset, r_attributes);
|
||||
float *attr_w = find_or_add_attribute(iter.name + "_w", size, vertex_offset, r_attributes);
|
||||
auto typed = attribute.typed<math::Quaternion>();
|
||||
for (const int64_t i : ply_to_vertex.index_range()) {
|
||||
int j = ply_to_vertex[i];
|
||||
attr_x[i] = typed[j].x;
|
||||
attr_y[i] = typed[j].y;
|
||||
attr_z[i] = typed[j].z;
|
||||
attr_w[i] = typed[j].w;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
BLI_assert_msg(0, "Unsupported attribute type for PLY export.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void load_plydata(PlyData &plyData, Depsgraph *depsgraph, const PLYExportParams &export_params)
|
||||
{
|
||||
DEGObjectIterSettings deg_iter_settings{};
|
||||
deg_iter_settings.depsgraph = depsgraph;
|
||||
deg_iter_settings.flags = DEG_ITER_OBJECT_FLAG_LINKED_DIRECTLY |
|
||||
DEG_ITER_OBJECT_FLAG_LINKED_VIA_SET | DEG_ITER_OBJECT_FLAG_VISIBLE |
|
||||
DEG_ITER_OBJECT_FLAG_DUPLI;
|
||||
|
||||
/* When exporting multiple objects, vertex indices have to be offset. */
|
||||
uint32_t vertex_offset = 0;
|
||||
|
||||
DEG_OBJECT_ITER_BEGIN (°_iter_settings, object) {
|
||||
if (object->type != OB_MESH) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (export_params.export_selected_objects && !(object->base_flag & BASE_SELECTED)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object *obj_eval = DEG_get_evaluated(depsgraph, object);
|
||||
|
||||
MeshCoerceForExport coerce;
|
||||
const Mesh *mesh = mesh_coerce_for_export_setup(
|
||||
coerce, depsgraph, obj_eval, export_params.apply_modifiers);
|
||||
|
||||
/* Ensure data exists if currently in edit mode. */
|
||||
BKE_mesh_wrapper_ensure_mdata(const_cast<Mesh *>(mesh));
|
||||
|
||||
bool force_triangulation = false;
|
||||
OffsetIndices faces = mesh->faces();
|
||||
for (const int i : faces.index_range()) {
|
||||
if (faces[i].size() > 255) {
|
||||
force_triangulation = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Triangulate */
|
||||
Mesh *manually_free_mesh = nullptr;
|
||||
if (export_params.export_triangulated_mesh || force_triangulation) {
|
||||
manually_free_mesh = do_triangulation(mesh, export_params.export_triangulated_mesh);
|
||||
mesh = manually_free_mesh;
|
||||
faces = mesh->faces();
|
||||
}
|
||||
|
||||
Vector<int> ply_to_vertex, vertex_to_ply, loop_to_ply;
|
||||
Vector<float2> uvs;
|
||||
generate_vertex_map(mesh, export_params, ply_to_vertex, vertex_to_ply, loop_to_ply, uvs);
|
||||
|
||||
float world_and_axes_transform[4][4];
|
||||
float world_and_axes_normal_transform[3][3];
|
||||
set_world_axes_transform(*obj_eval,
|
||||
export_params.forward_axis,
|
||||
export_params.up_axis,
|
||||
world_and_axes_transform,
|
||||
world_and_axes_normal_transform);
|
||||
|
||||
/* Face data. */
|
||||
plyData.face_vertices.reserve(plyData.face_vertices.size() + mesh->corners_num);
|
||||
for (const int corner : IndexRange(mesh->corners_num)) {
|
||||
int ply_index = loop_to_ply[corner];
|
||||
BLI_assert(ply_index >= 0 && ply_index < ply_to_vertex.size());
|
||||
plyData.face_vertices.append_unchecked(ply_index + vertex_offset);
|
||||
}
|
||||
|
||||
plyData.face_sizes.reserve(plyData.face_sizes.size() + mesh->faces_num);
|
||||
for (const int i : faces.index_range()) {
|
||||
const IndexRange face = faces[i];
|
||||
plyData.face_sizes.append_unchecked(face.size());
|
||||
}
|
||||
|
||||
/* Vertices */
|
||||
plyData.vertices.reserve(plyData.vertices.size() + ply_to_vertex.size());
|
||||
Span<float3> vert_positions = mesh->vert_positions();
|
||||
for (int vertex_index : ply_to_vertex) {
|
||||
float3 pos = vert_positions[vertex_index];
|
||||
mul_m4_v3(world_and_axes_transform, pos);
|
||||
mul_v3_fl(pos, export_params.global_scale);
|
||||
plyData.vertices.append_unchecked(pos);
|
||||
}
|
||||
|
||||
/* UV's */
|
||||
if (uvs.is_empty()) {
|
||||
uvs.append_n_times(float2(0), ply_to_vertex.size());
|
||||
}
|
||||
else {
|
||||
BLI_assert(uvs.size() == ply_to_vertex.size());
|
||||
plyData.uv_coordinates.extend(uvs);
|
||||
}
|
||||
|
||||
/* Normals */
|
||||
if (export_params.export_normals) {
|
||||
plyData.vertex_normals.reserve(plyData.vertex_normals.size() + ply_to_vertex.size());
|
||||
const Span<float3> vert_normals = mesh->vert_normals();
|
||||
for (int vertex_index : ply_to_vertex) {
|
||||
float3 normal = vert_normals[vertex_index];
|
||||
mul_m3_v3(world_and_axes_normal_transform, normal);
|
||||
normalize_v3(normal);
|
||||
plyData.vertex_normals.append(normal);
|
||||
}
|
||||
}
|
||||
|
||||
/* Colors */
|
||||
if (export_params.vertex_colors != ePLYVertexColorMode::None) {
|
||||
const StringRef name = mesh->active_color_attribute;
|
||||
if (!name.is_empty()) {
|
||||
const bke::AttributeAccessor attributes = mesh->attributes();
|
||||
const VArray color_attribute = *attributes.lookup_or_default<ColorGeometry4f>(
|
||||
name, bke::AttrDomain::Point, {0.0f, 0.0f, 0.0f, 0.0f});
|
||||
if (!color_attribute.is_empty()) {
|
||||
if (plyData.vertex_colors.size() != vertex_offset) {
|
||||
plyData.vertex_colors.resize(vertex_offset, float4(0));
|
||||
}
|
||||
|
||||
plyData.vertex_colors.reserve(vertex_offset + ply_to_vertex.size());
|
||||
for (int vertex_index : ply_to_vertex) {
|
||||
float4 color = float4(color_attribute[vertex_index]);
|
||||
if (export_params.vertex_colors == ePLYVertexColorMode::sRGB) {
|
||||
linearrgb_to_srgb_v4(color, color);
|
||||
}
|
||||
plyData.vertex_colors.append(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Custom attributes */
|
||||
if (export_params.export_attributes) {
|
||||
load_custom_attributes(mesh, ply_to_vertex, vertex_offset, plyData.vertex_custom_attr);
|
||||
}
|
||||
|
||||
/* Loose edges */
|
||||
Span<int2> edges = mesh->edges();
|
||||
mesh->loose_edges().foreach_index([&](const int i) {
|
||||
plyData.edges.append({vertex_to_ply[edges[i][0]], vertex_to_ply[edges[i][1]]});
|
||||
});
|
||||
|
||||
vertex_offset = int(plyData.vertices.size());
|
||||
if (manually_free_mesh) {
|
||||
BKE_id_free(nullptr, manually_free_mesh);
|
||||
}
|
||||
}
|
||||
|
||||
DEG_OBJECT_ITER_END;
|
||||
|
||||
/* Make sure color and attribute arrays are encompassing all input objects */
|
||||
if (!plyData.vertex_colors.is_empty()) {
|
||||
BLI_assert(plyData.vertex_colors.size() <= vertex_offset);
|
||||
plyData.vertex_colors.resize(vertex_offset, float4(0));
|
||||
}
|
||||
for (PlyCustomAttribute &attr : plyData.vertex_custom_attr) {
|
||||
BLI_assert(attr.data.size() <= vertex_offset);
|
||||
attr.data.resize(vertex_offset, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::io::ply
|
||||
@@ -0,0 +1,23 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Depsgraph;
|
||||
struct PLYExportParams;
|
||||
|
||||
namespace io::ply {
|
||||
|
||||
struct PlyData;
|
||||
|
||||
void load_plydata(PlyData &plyData, Depsgraph *depsgraph, const PLYExportParams &export_params);
|
||||
|
||||
} // namespace io::ply
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,89 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#include "ply_file_buffer.hh"
|
||||
|
||||
#include "BLI_fileops.hh"
|
||||
|
||||
#include <system_error>
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
static CLG_LogRef LOG = {"io.ply"};
|
||||
|
||||
namespace io::ply {
|
||||
|
||||
FileBuffer::FileBuffer(const char *filepath, size_t buffer_chunk_size)
|
||||
: buffer_chunk_size_(buffer_chunk_size), filepath_(filepath)
|
||||
{
|
||||
outfile_ = BLI_fopen(filepath, "wb");
|
||||
if (!outfile_) {
|
||||
throw std::system_error(
|
||||
errno, std::system_category(), "Cannot open file " + std::string(filepath) + ".");
|
||||
}
|
||||
}
|
||||
|
||||
void FileBuffer::write_to_file()
|
||||
{
|
||||
for (const VectorChar &b : blocks_) {
|
||||
fwrite(b.data(), 1, b.size(), this->outfile_);
|
||||
}
|
||||
blocks_.clear();
|
||||
}
|
||||
|
||||
void FileBuffer::close_file()
|
||||
{
|
||||
if (!outfile_) {
|
||||
return;
|
||||
}
|
||||
int close_status = std::fclose(outfile_);
|
||||
if (close_status == EOF) {
|
||||
return;
|
||||
}
|
||||
if (close_status) {
|
||||
CLOG_ERROR(&LOG, "Error: could not close file '%s' properly, it may be corrupted.", filepath_);
|
||||
}
|
||||
}
|
||||
|
||||
void FileBuffer::write_header_element(StringRef name, int count)
|
||||
{
|
||||
write_fstring("element {} {}\n", name, count);
|
||||
}
|
||||
void FileBuffer::write_header_scalar_property(StringRef dataType, StringRef name)
|
||||
{
|
||||
write_fstring("property {} {}\n", dataType, name);
|
||||
}
|
||||
|
||||
void FileBuffer::write_header_list_property(StringRef countType,
|
||||
StringRef dataType,
|
||||
StringRef name)
|
||||
{
|
||||
write_fstring("property list {} {} {}\n", countType, dataType, name);
|
||||
}
|
||||
|
||||
void FileBuffer::write_string(StringRef s)
|
||||
{
|
||||
write_fstring("{}\n", s);
|
||||
}
|
||||
|
||||
void FileBuffer::write_newline()
|
||||
{
|
||||
write_fstring("\n");
|
||||
}
|
||||
|
||||
void FileBuffer::write_bytes(Span<char> bytes)
|
||||
{
|
||||
ensure_space(bytes.size());
|
||||
VectorChar &bb = blocks_.last();
|
||||
bb.insert(bb.end(), bytes.begin(), bytes.end());
|
||||
}
|
||||
|
||||
} // namespace io::ply
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,94 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_utility_mixins.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace blender::io::ply {
|
||||
|
||||
/**
|
||||
* File buffer writer.
|
||||
* All writes are done into an internal chunked memory buffer
|
||||
* (list of default 64 kilobyte blocks).
|
||||
* Call write_to_file once in a while to write the memory buffer(s)
|
||||
* into the given file.
|
||||
*/
|
||||
class FileBuffer : private NonMovable {
|
||||
using VectorChar = Vector<char>;
|
||||
Vector<VectorChar> blocks_;
|
||||
size_t buffer_chunk_size_;
|
||||
const char *filepath_;
|
||||
FILE *outfile_;
|
||||
|
||||
public:
|
||||
FileBuffer(const char *filepath, size_t buffer_chunk_size = 64 * 1024);
|
||||
|
||||
virtual ~FileBuffer() = default;
|
||||
|
||||
/* Write contents to the buffer(s) into a file, and clear the buffers. */
|
||||
void write_to_file();
|
||||
|
||||
void close_file();
|
||||
|
||||
virtual void write_vertex(float x, float y, float z) = 0;
|
||||
|
||||
virtual void write_UV(float u, float v) = 0;
|
||||
|
||||
virtual void write_data(float v) = 0;
|
||||
|
||||
virtual void write_vertex_normal(float nx, float ny, float nz) = 0;
|
||||
|
||||
virtual void write_vertex_color(uchar r, uchar g, uchar b, uchar a) = 0;
|
||||
|
||||
virtual void write_vertex_end() = 0;
|
||||
|
||||
virtual void write_face(char count, Span<uint32_t> const &vertex_indices) = 0;
|
||||
|
||||
virtual void write_edge(int first, int second) = 0;
|
||||
|
||||
void write_header_element(StringRef name, int count);
|
||||
|
||||
void write_header_scalar_property(StringRef dataType, StringRef name);
|
||||
|
||||
void write_header_list_property(StringRef countType, StringRef dataType, StringRef name);
|
||||
|
||||
void write_string(StringRef s);
|
||||
|
||||
void write_newline();
|
||||
|
||||
protected:
|
||||
/* Ensure the last block contains at least this amount of free space.
|
||||
* If not, add a new block with max of block size & the amount of space needed. */
|
||||
void ensure_space(size_t at_least)
|
||||
{
|
||||
if (blocks_.is_empty() || (blocks_.last().capacity() - blocks_.last().size() < at_least)) {
|
||||
blocks_.append(VectorChar());
|
||||
blocks_.last().reserve(std::max(at_least, buffer_chunk_size_));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... T> void write_fstring(fmt::format_string<T...> fmt, T &&...args)
|
||||
{
|
||||
/* Format into a local buffer. */
|
||||
fmt::memory_buffer buf;
|
||||
fmt::format_to(fmt::appender(buf), fmt, std::forward<T>(args)...);
|
||||
size_t len = buf.size();
|
||||
ensure_space(len);
|
||||
VectorChar &bb = blocks_.last();
|
||||
bb.insert(bb.end(), buf.begin(), buf.end());
|
||||
}
|
||||
|
||||
void write_bytes(Span<char> bytes);
|
||||
};
|
||||
|
||||
} // namespace blender::io::ply
|
||||
@@ -0,0 +1,58 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#include "ply_file_buffer_ascii.hh"
|
||||
|
||||
namespace blender::io::ply {
|
||||
|
||||
void FileBufferAscii::write_vertex(float x, float y, float z)
|
||||
{
|
||||
write_fstring("{} {} {}", x, y, z);
|
||||
}
|
||||
|
||||
void FileBufferAscii::write_UV(float u, float v)
|
||||
{
|
||||
write_fstring(" {} {}", u, v);
|
||||
}
|
||||
|
||||
void FileBufferAscii::write_data(float v)
|
||||
{
|
||||
write_fstring(" {}", v);
|
||||
}
|
||||
|
||||
void FileBufferAscii::write_vertex_normal(float nx, float ny, float nz)
|
||||
{
|
||||
write_fstring(" {} {} {}", nx, ny, nz);
|
||||
}
|
||||
|
||||
void FileBufferAscii::write_vertex_color(uchar r, uchar g, uchar b, uchar a)
|
||||
{
|
||||
write_fstring(" {} {} {} {}", r, g, b, a);
|
||||
}
|
||||
|
||||
void FileBufferAscii::write_vertex_end()
|
||||
{
|
||||
write_fstring("\n");
|
||||
}
|
||||
|
||||
void FileBufferAscii::write_face(char count, Span<uint32_t> const &vertex_indices)
|
||||
{
|
||||
write_fstring("{}", int(count));
|
||||
|
||||
for (const uint32_t v : vertex_indices) {
|
||||
write_fstring(" {}", v);
|
||||
}
|
||||
write_newline();
|
||||
}
|
||||
|
||||
void FileBufferAscii::write_edge(int first, int second)
|
||||
{
|
||||
write_fstring("{} {}", first, second);
|
||||
write_newline();
|
||||
}
|
||||
} // namespace blender::io::ply
|
||||
@@ -0,0 +1,34 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ply_file_buffer.hh"
|
||||
|
||||
namespace blender::io::ply {
|
||||
class FileBufferAscii : public FileBuffer {
|
||||
using FileBuffer::FileBuffer;
|
||||
|
||||
public:
|
||||
void write_vertex(float x, float y, float z) override;
|
||||
|
||||
void write_UV(float u, float v) override;
|
||||
|
||||
void write_data(float v) override;
|
||||
|
||||
void write_vertex_normal(float nx, float ny, float nz) override;
|
||||
|
||||
void write_vertex_color(uchar r, uchar g, uchar b, uchar a) override;
|
||||
|
||||
void write_vertex_end() override;
|
||||
|
||||
void write_face(char count, Span<uint32_t> const &vertex_indices) override;
|
||||
|
||||
void write_edge(int first, int second) override;
|
||||
};
|
||||
} // namespace blender::io::ply
|
||||
@@ -0,0 +1,78 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#include "ply_file_buffer_binary.hh"
|
||||
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
namespace blender::io::ply {
|
||||
void FileBufferBinary::write_vertex(float x, float y, float z)
|
||||
{
|
||||
float3 vector(x, y, z);
|
||||
char *bits = reinterpret_cast<char *>(&vector);
|
||||
Span<char> span(bits, sizeof(float3));
|
||||
|
||||
write_bytes(span);
|
||||
}
|
||||
|
||||
void FileBufferBinary::write_UV(float u, float v)
|
||||
{
|
||||
float2 vector(u, v);
|
||||
char *bits = reinterpret_cast<char *>(&vector);
|
||||
Span<char> span(bits, sizeof(float2));
|
||||
|
||||
write_bytes(span);
|
||||
}
|
||||
|
||||
void FileBufferBinary::write_data(float v)
|
||||
{
|
||||
char *bits = reinterpret_cast<char *>(&v);
|
||||
Span<char> span(bits, sizeof(float));
|
||||
|
||||
write_bytes(span);
|
||||
}
|
||||
|
||||
void FileBufferBinary::write_vertex_normal(float nx, float ny, float nz)
|
||||
{
|
||||
float3 vector(nx, ny, nz);
|
||||
char *bits = reinterpret_cast<char *>(&vector);
|
||||
Span<char> span(bits, sizeof(float3));
|
||||
|
||||
write_bytes(span);
|
||||
}
|
||||
|
||||
void FileBufferBinary::write_vertex_color(uchar r, uchar g, uchar b, uchar a)
|
||||
{
|
||||
uchar4 vector(r, g, b, a);
|
||||
char *bits = reinterpret_cast<char *>(&vector);
|
||||
Span<char> span(bits, sizeof(uchar4));
|
||||
|
||||
write_bytes(span);
|
||||
}
|
||||
|
||||
void FileBufferBinary::write_vertex_end()
|
||||
{
|
||||
/* In binary, there is no end to a vertex. */
|
||||
}
|
||||
|
||||
void FileBufferBinary::write_face(char size, Span<uint32_t> const &vertex_indices)
|
||||
{
|
||||
write_bytes(Span<char>({size}));
|
||||
|
||||
write_bytes(vertex_indices.cast<char>());
|
||||
}
|
||||
|
||||
void FileBufferBinary::write_edge(int first, int second)
|
||||
{
|
||||
int2 vector(first, second);
|
||||
char *bits = reinterpret_cast<char *>(&vector);
|
||||
Span<char> span(bits, sizeof(int2));
|
||||
|
||||
write_bytes(span);
|
||||
}
|
||||
} // namespace blender::io::ply
|
||||
@@ -0,0 +1,34 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ply_file_buffer.hh"
|
||||
|
||||
namespace blender::io::ply {
|
||||
class FileBufferBinary : public FileBuffer {
|
||||
using FileBuffer::FileBuffer;
|
||||
|
||||
public:
|
||||
void write_vertex(float x, float y, float z) override;
|
||||
|
||||
void write_UV(float u, float v) override;
|
||||
|
||||
void write_data(float v) override;
|
||||
|
||||
void write_vertex_normal(float nx, float ny, float nz) override;
|
||||
|
||||
void write_vertex_color(uchar r, uchar g, uchar b, uchar a) override;
|
||||
|
||||
void write_vertex_end() override;
|
||||
|
||||
void write_face(char size, Span<uint32_t> const &vertex_indices) override;
|
||||
|
||||
void write_edge(int first, int second) override;
|
||||
};
|
||||
} // namespace blender::io::ply
|
||||
286
blender-5.2.0/source/blender/io/ply/importer/ply_import.cc
Normal file
286
blender-5.2.0/source/blender/io/ply/importer/ply_import.cc
Normal file
@@ -0,0 +1,286 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#include "BKE_context.hh"
|
||||
#include "BKE_layer.hh"
|
||||
#include "BKE_library.hh"
|
||||
#include "BKE_mesh.hh"
|
||||
#include "BKE_object.hh"
|
||||
#include "BKE_report.hh"
|
||||
|
||||
#include "DNA_collection_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
#include "DNA_scene_types.h"
|
||||
|
||||
#include "BLI_math_matrix.h"
|
||||
#include "BLI_math_rotation.h"
|
||||
#include "BLI_span.hh"
|
||||
#include "BLI_string.h"
|
||||
|
||||
#include "DEG_depsgraph.hh"
|
||||
#include "DEG_depsgraph_build.hh"
|
||||
|
||||
#include "ply_data.hh"
|
||||
#include "ply_import.hh"
|
||||
#include "ply_import_buffer.hh"
|
||||
#include "ply_import_data.hh"
|
||||
#include "ply_import_mesh.hh"
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
static CLG_LogRef LOG = {"io.ply"};
|
||||
|
||||
namespace io::ply {
|
||||
|
||||
/* If line starts with keyword, returns true and drops it from the line. */
|
||||
static bool parse_keyword(Span<char> &str, StringRef keyword)
|
||||
{
|
||||
const size_t keyword_len = keyword.size();
|
||||
if (str.size() < keyword_len) {
|
||||
return false;
|
||||
}
|
||||
if (memcmp(str.data(), keyword.data(), keyword_len) != 0) {
|
||||
return false;
|
||||
}
|
||||
str = str.drop_front(keyword_len);
|
||||
return true;
|
||||
}
|
||||
|
||||
static Span<char> parse_word(Span<char> &str)
|
||||
{
|
||||
size_t len = 0;
|
||||
while (len < str.size() && str[len] > ' ') {
|
||||
++len;
|
||||
}
|
||||
Span<char> word(str.begin(), len);
|
||||
str = str.drop_front(len);
|
||||
return word;
|
||||
}
|
||||
|
||||
static void skip_space(Span<char> &str)
|
||||
{
|
||||
while (!str.is_empty() && str[0] <= ' ') {
|
||||
str = str.drop_front(1);
|
||||
}
|
||||
}
|
||||
|
||||
static PlyDataTypes type_from_string(Span<char> word)
|
||||
{
|
||||
StringRef input(word.data(), word.size());
|
||||
if (ELEM(input, "uchar", "uint8")) {
|
||||
return PlyDataTypes::UCHAR;
|
||||
}
|
||||
if (ELEM(input, "char", "int8")) {
|
||||
return PlyDataTypes::CHAR;
|
||||
}
|
||||
if (ELEM(input, "ushort", "uint16")) {
|
||||
return PlyDataTypes::USHORT;
|
||||
}
|
||||
if (ELEM(input, "short", "int16")) {
|
||||
return PlyDataTypes::SHORT;
|
||||
}
|
||||
if (ELEM(input, "uint", "uint32")) {
|
||||
return PlyDataTypes::UINT;
|
||||
}
|
||||
if (ELEM(input, "int", "int32")) {
|
||||
return PlyDataTypes::INT;
|
||||
}
|
||||
if (ELEM(input, "float", "float32")) {
|
||||
return PlyDataTypes::FLOAT;
|
||||
}
|
||||
if (ELEM(input, "double", "float64")) {
|
||||
return PlyDataTypes::DOUBLE;
|
||||
}
|
||||
return PlyDataTypes::NONE;
|
||||
}
|
||||
|
||||
const char *read_header(PlyReadBuffer &file, PlyHeader &r_header)
|
||||
{
|
||||
Span<char> word, line;
|
||||
line = file.read_line();
|
||||
if (StringRef(line.data(), line.size()) != "ply") {
|
||||
return "Invalid PLY header.";
|
||||
}
|
||||
|
||||
while (true) { /* We break when end_header is encountered. */
|
||||
line = file.read_line();
|
||||
|
||||
if (parse_keyword(line, "format")) {
|
||||
skip_space(line);
|
||||
if (parse_keyword(line, "ascii")) {
|
||||
r_header.type = PlyFormatType::ASCII;
|
||||
}
|
||||
else if (parse_keyword(line, "binary_big_endian")) {
|
||||
r_header.type = PlyFormatType::BINARY_BE;
|
||||
}
|
||||
else if (parse_keyword(line, "binary_little_endian")) {
|
||||
r_header.type = PlyFormatType::BINARY_LE;
|
||||
}
|
||||
}
|
||||
else if (parse_keyword(line, "element")) {
|
||||
PlyElement element;
|
||||
|
||||
skip_space(line);
|
||||
word = parse_word(line);
|
||||
element.name = std::string(word.data(), word.size());
|
||||
skip_space(line);
|
||||
word = parse_word(line);
|
||||
element.count = std::stoi(std::string(word.data(), word.size()));
|
||||
r_header.elements.append(element);
|
||||
}
|
||||
else if (parse_keyword(line, "property")) {
|
||||
PlyProperty property;
|
||||
skip_space(line);
|
||||
if (parse_keyword(line, "list")) {
|
||||
skip_space(line);
|
||||
property.count_type = type_from_string(parse_word(line));
|
||||
}
|
||||
skip_space(line);
|
||||
property.type = type_from_string(parse_word(line));
|
||||
skip_space(line);
|
||||
word = parse_word(line);
|
||||
property.name = std::string(word.data(), word.size());
|
||||
r_header.elements.last().properties.append(property);
|
||||
}
|
||||
else if (parse_keyword(line, "end_header")) {
|
||||
break;
|
||||
}
|
||||
else if (line.is_empty() || (line.first() >= '0' && line.first() <= '9') ||
|
||||
line.first() == '-')
|
||||
{
|
||||
/* A value was found before we broke out of the loop. No end_header. */
|
||||
return "No end_header.";
|
||||
}
|
||||
}
|
||||
|
||||
file.after_header(r_header.type != PlyFormatType::ASCII);
|
||||
for (PlyElement &el : r_header.elements) {
|
||||
el.calc_stride();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static Mesh *read_ply_to_mesh(const PLYImportParams &import_params, const char *ob_name)
|
||||
{
|
||||
/* Parse header. */
|
||||
PlyReadBuffer file(import_params.filepath, 64 * 1024);
|
||||
|
||||
PlyHeader header;
|
||||
const char *err = read_header(file, header);
|
||||
if (err != nullptr) {
|
||||
CLOG_ERROR(&LOG, "PLY Importer: %s: %s", ob_name, err);
|
||||
BKE_reportf(import_params.reports, RPT_ERROR, "PLY Importer: %s: %s", ob_name, err);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Parse actual file data. */
|
||||
std::unique_ptr<PlyData> data = import_ply_data(file, header);
|
||||
if (data == nullptr) {
|
||||
CLOG_ERROR(&LOG, "PLY Importer: failed importing %s, unknown error", ob_name);
|
||||
BKE_report(import_params.reports, RPT_ERROR, "PLY Importer: failed importing, unknown error");
|
||||
return nullptr;
|
||||
}
|
||||
if (!data->error.empty()) {
|
||||
CLOG_ERROR(&LOG, "PLY Importer: failed importing %s: %s", ob_name, data->error.c_str());
|
||||
BKE_report(import_params.reports, RPT_ERROR, "PLY Importer: failed importing, unknown error");
|
||||
return nullptr;
|
||||
}
|
||||
if (data->vertices.is_empty()) {
|
||||
CLOG_ERROR(&LOG, "PLY Importer: file %s contains no vertices", ob_name);
|
||||
BKE_report(import_params.reports, RPT_ERROR, "PLY Importer: failed importing, no vertices");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return convert_ply_to_mesh(*data, import_params);
|
||||
}
|
||||
|
||||
Mesh *import_mesh(const PLYImportParams &import_params)
|
||||
{
|
||||
/* File base name used for both mesh and object. */
|
||||
char ob_name[FILE_MAX];
|
||||
STRNCPY(ob_name, BLI_path_basename(import_params.filepath));
|
||||
BLI_path_extension_strip(ob_name);
|
||||
|
||||
/* Stuff ply data into the mesh. */
|
||||
return read_ply_to_mesh(import_params, ob_name);
|
||||
}
|
||||
|
||||
void importer_main(bContext *C, const PLYImportParams &import_params)
|
||||
{
|
||||
Main *bmain = CTX_data_main(C);
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
ViewLayer *view_layer = CTX_data_view_layer(C);
|
||||
importer_main(bmain, scene, view_layer, import_params);
|
||||
}
|
||||
|
||||
void importer_main(Main *bmain,
|
||||
Scene *scene,
|
||||
ViewLayer *view_layer,
|
||||
const PLYImportParams &import_params)
|
||||
{
|
||||
/* File base name used for both mesh and object. */
|
||||
char ob_name[FILE_MAX];
|
||||
STRNCPY(ob_name, BLI_path_basename(import_params.filepath));
|
||||
BLI_path_extension_strip(ob_name);
|
||||
|
||||
/* Stuff ply data into the mesh. */
|
||||
Mesh *mesh = read_ply_to_mesh(import_params, ob_name);
|
||||
|
||||
if (mesh == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Create mesh and do all prep work. */
|
||||
Mesh *mesh_in_main = BKE_mesh_add(bmain, ob_name);
|
||||
BKE_view_layer_base_deselect_all(*bmain, scene, view_layer);
|
||||
LayerCollection *lc = BKE_layer_collection_get_active_editable(view_layer);
|
||||
if (!ID_IS_EDITABLE(lc->collection)) {
|
||||
BKE_report(import_params.reports,
|
||||
RPT_WARNING,
|
||||
"Could not find an editable collection in current scene, imported data will not be "
|
||||
"instantiated");
|
||||
}
|
||||
Object *obj = BKE_object_add_only_object(bmain, OB_MESH, ob_name);
|
||||
obj->data = id_cast<ID *>(mesh_in_main);
|
||||
BKE_collection_object_add(bmain, lc->collection, obj);
|
||||
BKE_view_layer_synced_ensure(*bmain, scene, view_layer);
|
||||
if (Base *base = BKE_view_layer_base_find(view_layer, obj)) {
|
||||
/* `base` will be nullptr if the Object could not be instantiated in the current viewlayer. */
|
||||
BKE_view_layer_base_select_and_set_active(view_layer, base);
|
||||
}
|
||||
|
||||
BKE_mesh_nomain_to_mesh(mesh, mesh_in_main, obj);
|
||||
|
||||
/* Object matrix and finishing up. */
|
||||
float global_scale = import_params.global_scale;
|
||||
if ((scene->unit.system != USER_UNIT_NONE) && import_params.use_scene_unit) {
|
||||
global_scale /= scene->unit.scale_length;
|
||||
}
|
||||
float scale_vec[3] = {global_scale, global_scale, global_scale};
|
||||
float obmat3x3[3][3];
|
||||
unit_m3(obmat3x3);
|
||||
float obmat4x4[4][4];
|
||||
unit_m4(obmat4x4);
|
||||
/* +Y-forward and +Z-up are the Blender's default axis settings. */
|
||||
mat3_from_axis_conversion(
|
||||
IO_AXIS_Y, IO_AXIS_Z, import_params.forward_axis, import_params.up_axis, obmat3x3);
|
||||
copy_m4_m3(obmat4x4, obmat3x3);
|
||||
rescale_m4(obmat4x4, scale_vec);
|
||||
BKE_object_apply_mat4(obj, obmat4x4, true, false);
|
||||
|
||||
DEG_id_tag_update(&lc->collection->id, ID_RECALC_SYNC_TO_EVAL);
|
||||
int flags = ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION |
|
||||
ID_RECALC_BASE_FLAGS;
|
||||
DEG_id_tag_update_ex(bmain, &obj->id, flags);
|
||||
DEG_id_tag_update(&scene->id, ID_RECALC_BASE_FLAGS);
|
||||
DEG_relations_tag_update(bmain);
|
||||
}
|
||||
} // namespace io::ply
|
||||
} // namespace blender
|
||||
40
blender-5.2.0/source/blender/io/ply/importer/ply_import.hh
Normal file
40
blender-5.2.0/source/blender/io/ply/importer/ply_import.hh
Normal file
@@ -0,0 +1,40 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IO_ply.hh"
|
||||
#include "ply_data.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct bContext;
|
||||
struct Mesh;
|
||||
struct Main;
|
||||
struct Scene;
|
||||
struct ViewLayer;
|
||||
|
||||
namespace io::ply {
|
||||
|
||||
class PlyReadBuffer;
|
||||
|
||||
Mesh *import_mesh(const PLYImportParams &import_params);
|
||||
|
||||
/* Main import function used from within Blender. */
|
||||
void importer_main(bContext *C, const PLYImportParams &import_params);
|
||||
|
||||
/* Used from tests, where full bContext does not exist. */
|
||||
void importer_main(Main *bmain,
|
||||
Scene *scene,
|
||||
ViewLayer *view_layer,
|
||||
const PLYImportParams &import_params);
|
||||
|
||||
const char *read_header(PlyReadBuffer &file, PlyHeader &r_header);
|
||||
|
||||
} // namespace io::ply
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,131 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "ply_import_buffer.hh"
|
||||
|
||||
#include "BLI_fileops.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace blender {
|
||||
|
||||
static inline bool is_newline(char ch)
|
||||
{
|
||||
return ch == '\n';
|
||||
}
|
||||
|
||||
namespace io::ply {
|
||||
|
||||
PlyReadBuffer::PlyReadBuffer(const char *file_path, size_t read_buffer_size)
|
||||
: buffer_(read_buffer_size), read_buffer_size_(read_buffer_size)
|
||||
{
|
||||
file_ = BLI_fopen(file_path, "rb");
|
||||
}
|
||||
|
||||
PlyReadBuffer::~PlyReadBuffer()
|
||||
{
|
||||
if (file_ != nullptr) {
|
||||
fclose(file_);
|
||||
}
|
||||
}
|
||||
|
||||
void PlyReadBuffer::after_header(bool is_binary)
|
||||
{
|
||||
is_binary_ = is_binary;
|
||||
}
|
||||
|
||||
Span<char> PlyReadBuffer::read_line()
|
||||
{
|
||||
if (is_binary_) {
|
||||
throw std::runtime_error("PLY read_line should not be used in binary mode");
|
||||
}
|
||||
if (pos_ >= last_newline_) {
|
||||
refill_buffer();
|
||||
}
|
||||
BLI_assert(last_newline_ <= buffer_.size());
|
||||
int res_begin = pos_;
|
||||
while (pos_ < last_newline_ && !is_newline(buffer_[pos_])) {
|
||||
pos_++;
|
||||
}
|
||||
int res_end = pos_;
|
||||
/* Remove possible trailing CR from the result. */
|
||||
if (res_end > res_begin && buffer_[res_end - 1] == '\r') {
|
||||
--res_end;
|
||||
}
|
||||
/* Move cursor past newline. */
|
||||
if (pos_ < buf_used_ && is_newline(buffer_[pos_])) {
|
||||
pos_++;
|
||||
}
|
||||
return Span<char>(buffer_.data() + res_begin, res_end - res_begin);
|
||||
}
|
||||
|
||||
bool PlyReadBuffer::read_bytes(void *dst, size_t size)
|
||||
{
|
||||
while (size > 0) {
|
||||
if (pos_ + size > buf_used_) {
|
||||
if (!refill_buffer()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
int to_copy = int(size);
|
||||
to_copy = std::min(to_copy, buf_used_);
|
||||
memcpy(dst, buffer_.data() + pos_, to_copy);
|
||||
pos_ += to_copy;
|
||||
dst = static_cast<char *>(dst) + to_copy;
|
||||
size -= to_copy;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlyReadBuffer::refill_buffer()
|
||||
{
|
||||
BLI_assert(pos_ <= buf_used_);
|
||||
BLI_assert(pos_ <= buffer_.size());
|
||||
BLI_assert(buf_used_ <= buffer_.size());
|
||||
|
||||
if (file_ == nullptr || at_eof_) {
|
||||
return false; /* File is fully read. */
|
||||
}
|
||||
|
||||
/* Move any leftover to start of buffer. */
|
||||
int keep = buf_used_ - pos_;
|
||||
if (keep > 0) {
|
||||
memmove(buffer_.data(), buffer_.data() + pos_, keep);
|
||||
}
|
||||
/* Read in data from the file. */
|
||||
size_t read = fread(buffer_.data() + keep, 1, read_buffer_size_ - keep, file_) + keep;
|
||||
at_eof_ = read < read_buffer_size_;
|
||||
pos_ = 0;
|
||||
buf_used_ = int(read);
|
||||
|
||||
/* Skip past newlines at the front of the buffer and find last newline. */
|
||||
if (!is_binary_) {
|
||||
while (pos_ < buf_used_ && is_newline(buffer_[pos_])) {
|
||||
pos_++;
|
||||
}
|
||||
|
||||
int last_nl = buf_used_;
|
||||
if (!at_eof_) {
|
||||
while (last_nl > 0) {
|
||||
--last_nl;
|
||||
if (is_newline(buffer_[last_nl])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!is_newline(buffer_[last_nl])) {
|
||||
/* Whole line did not fit into our read buffer. */
|
||||
throw std::runtime_error("PLY text line did not fit into the read buffer");
|
||||
}
|
||||
}
|
||||
last_newline_ = last_nl;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace io::ply
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,54 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include "BLI_array.hh"
|
||||
#include "BLI_span.hh"
|
||||
|
||||
namespace blender::io::ply {
|
||||
|
||||
/**
|
||||
* Reads underlying PLY file in large chunks, and provides interface for ASCII/header
|
||||
* parsing to read individual lines, and for binary parsing to read chunks of bytes.
|
||||
*/
|
||||
class PlyReadBuffer {
|
||||
public:
|
||||
PlyReadBuffer(const char *file_path, size_t read_buffer_size = 64 * 1024);
|
||||
~PlyReadBuffer();
|
||||
|
||||
/** After header is parsed, indicate whether the rest of reading will be ASCII or binary. */
|
||||
void after_header(bool is_binary);
|
||||
|
||||
/**
|
||||
* Gets the next line from the file as a Span. The line does not include any newline characters.
|
||||
*/
|
||||
Span<char> read_line();
|
||||
|
||||
/**
|
||||
* Reads a number of bytes into provided destination pointer. Returns false if this amount of
|
||||
* bytes can not be read.
|
||||
*/
|
||||
bool read_bytes(void *dst, size_t size);
|
||||
|
||||
private:
|
||||
bool refill_buffer();
|
||||
|
||||
FILE *file_ = nullptr;
|
||||
Array<char> buffer_;
|
||||
int pos_ = 0;
|
||||
int buf_used_ = 0;
|
||||
int last_newline_ = 0;
|
||||
size_t read_buffer_size_ = 0;
|
||||
bool at_eof_ = false;
|
||||
bool is_binary_ = false;
|
||||
};
|
||||
|
||||
} // namespace blender::io::ply
|
||||
687
blender-5.2.0/source/blender/io/ply/importer/ply_import_data.cc
Normal file
687
blender-5.2.0/source/blender/io/ply/importer/ply_import_data.cc
Normal file
@@ -0,0 +1,687 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#include "ply_import_data.hh"
|
||||
#include "ply_data.hh"
|
||||
#include "ply_import_buffer.hh"
|
||||
|
||||
#include "BLI_endian_switch.h"
|
||||
#include "BLI_string_ref.hh"
|
||||
|
||||
#include "fast_float.h"
|
||||
|
||||
#include <charconv>
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
static CLG_LogRef LOG = {"io.ply"};
|
||||
|
||||
static bool is_whitespace(char c)
|
||||
{
|
||||
return c <= ' ';
|
||||
}
|
||||
|
||||
static const char *drop_whitespace(const char *p, const char *end)
|
||||
{
|
||||
while (p < end && is_whitespace(*p)) {
|
||||
++p;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
static const char *drop_non_whitespace(const char *p, const char *end)
|
||||
{
|
||||
while (p < end && !is_whitespace(*p)) {
|
||||
++p;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
static const char *drop_plus(const char *p, const char *end)
|
||||
{
|
||||
if (p < end && *p == '+') {
|
||||
++p;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
static const char *parse_float(const char *p, const char *end, float fallback, float &dst)
|
||||
{
|
||||
p = drop_whitespace(p, end);
|
||||
p = drop_plus(p, end);
|
||||
fast_float::from_chars_result res = fast_float::from_chars(p, end, dst);
|
||||
if (ELEM(res.ec, std::errc::invalid_argument, std::errc::result_out_of_range)) {
|
||||
dst = fallback;
|
||||
}
|
||||
return res.ptr;
|
||||
}
|
||||
|
||||
static const char *parse_int(const char *p, const char *end, int fallback, int &dst)
|
||||
{
|
||||
p = drop_whitespace(p, end);
|
||||
p = drop_plus(p, end);
|
||||
std::from_chars_result res = std::from_chars(p, end, dst);
|
||||
if (ELEM(res.ec, std::errc::invalid_argument, std::errc::result_out_of_range)) {
|
||||
dst = fallback;
|
||||
}
|
||||
return res.ptr;
|
||||
}
|
||||
|
||||
static void endian_switch(uint8_t *ptr, int type_size)
|
||||
{
|
||||
if (type_size == 2) {
|
||||
BLI_endian_switch_uint16(reinterpret_cast<uint16_t *>(ptr));
|
||||
}
|
||||
else if (type_size == 4) {
|
||||
BLI_endian_switch_uint32(reinterpret_cast<uint32_t *>(ptr));
|
||||
}
|
||||
else if (type_size == 8) {
|
||||
BLI_endian_switch_uint64(reinterpret_cast<uint64_t *>(ptr));
|
||||
}
|
||||
}
|
||||
|
||||
static void endian_switch_array(uint8_t *ptr, int type_size, int size)
|
||||
{
|
||||
if (type_size == 2) {
|
||||
BLI_endian_switch_uint16_array(reinterpret_cast<uint16_t *>(ptr), size);
|
||||
}
|
||||
else if (type_size == 4) {
|
||||
BLI_endian_switch_uint32_array(reinterpret_cast<uint32_t *>(ptr), size);
|
||||
}
|
||||
else if (type_size == 8) {
|
||||
BLI_endian_switch_uint64_array(reinterpret_cast<uint64_t *>(ptr), size);
|
||||
}
|
||||
}
|
||||
|
||||
namespace io::ply {
|
||||
|
||||
static const int data_type_size[] = {0, 1, 1, 2, 2, 4, 4, 4, 8};
|
||||
static_assert(std::size(data_type_size) == PLY_TYPE_COUNT, "PLY data type size table mismatch");
|
||||
|
||||
static const float data_type_normalizer[] = {
|
||||
1.0f, 127.0f, 255.0f, 32767.0f, 65535.0f, float(INT_MAX), float(UINT_MAX), 1.0f, 1.0f};
|
||||
static_assert(std::size(data_type_normalizer) == PLY_TYPE_COUNT,
|
||||
"PLY data type normalization factor table mismatch");
|
||||
|
||||
void PlyElement::calc_stride()
|
||||
{
|
||||
stride = 0;
|
||||
for (PlyProperty &p : properties) {
|
||||
if (p.count_type != PlyDataTypes::NONE) {
|
||||
stride = 0;
|
||||
return;
|
||||
}
|
||||
stride += data_type_size[p.type];
|
||||
}
|
||||
}
|
||||
|
||||
static int get_index(const PlyElement &element, StringRef property)
|
||||
{
|
||||
for (int i = 0, n = int(element.properties.size()); i != n; i++) {
|
||||
const PlyProperty &prop = element.properties[i];
|
||||
if (prop.name == property) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static const char *parse_row_ascii(PlyReadBuffer &file, Vector<float> &r_values)
|
||||
{
|
||||
Span<char> line = file.read_line();
|
||||
if (line.is_empty()) {
|
||||
return "Could not read row of ASCII property";
|
||||
}
|
||||
|
||||
/* Parse whole line as floats. */
|
||||
const char *p = line.data();
|
||||
const char *end = p + line.size();
|
||||
int value_idx = 0;
|
||||
while (p < end && value_idx < r_values.size()) {
|
||||
float val;
|
||||
p = parse_float(p, end, 0.0f, val);
|
||||
r_values[value_idx++] = val;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template<typename T> static T get_binary_value(PlyDataTypes type, const uint8_t *&r_ptr)
|
||||
{
|
||||
T val = 0;
|
||||
switch (type) {
|
||||
case NONE:
|
||||
break;
|
||||
case CHAR:
|
||||
val = *reinterpret_cast<int8_t *>(const_cast<uint8_t *>(r_ptr));
|
||||
r_ptr += 1;
|
||||
break;
|
||||
case UCHAR:
|
||||
val = *const_cast<uint8_t *>(r_ptr);
|
||||
r_ptr += 1;
|
||||
break;
|
||||
case SHORT:
|
||||
val = *reinterpret_cast<int16_t *>(const_cast<uint8_t *>(r_ptr));
|
||||
r_ptr += 2;
|
||||
break;
|
||||
case USHORT:
|
||||
val = *reinterpret_cast<uint16_t *>(const_cast<uint8_t *>(r_ptr));
|
||||
r_ptr += 2;
|
||||
break;
|
||||
case INT:
|
||||
val = *reinterpret_cast<int32_t *>(const_cast<uint8_t *>(r_ptr));
|
||||
r_ptr += 4;
|
||||
break;
|
||||
case UINT:
|
||||
val = *reinterpret_cast<int32_t *>(const_cast<uint8_t *>(r_ptr));
|
||||
r_ptr += 4;
|
||||
break;
|
||||
case FLOAT:
|
||||
val = *reinterpret_cast<float *>(const_cast<uint8_t *>(r_ptr));
|
||||
r_ptr += 4;
|
||||
break;
|
||||
case DOUBLE:
|
||||
val = *reinterpret_cast<double *>(const_cast<uint8_t *>(r_ptr));
|
||||
r_ptr += 8;
|
||||
break;
|
||||
default:
|
||||
BLI_assert_msg(false, "Unknown property type");
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
static const char *parse_row_binary(PlyReadBuffer &file,
|
||||
const PlyHeader &header,
|
||||
const PlyElement &element,
|
||||
Vector<uint8_t> &r_scratch,
|
||||
Vector<float> &r_values)
|
||||
{
|
||||
if (element.stride == 0) {
|
||||
return "Vertex/Edge element contains list properties, this is not supported";
|
||||
}
|
||||
BLI_assert(r_scratch.size() == element.stride);
|
||||
BLI_assert(r_values.size() == element.properties.size());
|
||||
if (!file.read_bytes(r_scratch.data(), r_scratch.size())) {
|
||||
return "Could not read row of binary property";
|
||||
}
|
||||
|
||||
const uint8_t *ptr = r_scratch.data();
|
||||
if (header.type == PlyFormatType::BINARY_LE) {
|
||||
/* Little endian: just read/convert the values. */
|
||||
for (int i = 0, n = int(element.properties.size()); i != n; i++) {
|
||||
const PlyProperty &prop = element.properties[i];
|
||||
float val = get_binary_value<float>(prop.type, ptr);
|
||||
r_values[i] = val;
|
||||
}
|
||||
}
|
||||
else if (header.type == PlyFormatType::BINARY_BE) {
|
||||
/* Big endian: read, switch endian, convert the values. */
|
||||
for (int i = 0, n = int(element.properties.size()); i != n; i++) {
|
||||
const PlyProperty &prop = element.properties[i];
|
||||
endian_switch(const_cast<uint8_t *>(ptr), data_type_size[prop.type]);
|
||||
float val = get_binary_value<float>(prop.type, ptr);
|
||||
r_values[i] = val;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return "Unknown binary ply format for vertex element";
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static const char *load_vertex_element(PlyReadBuffer &file,
|
||||
const PlyHeader &header,
|
||||
const PlyElement &element,
|
||||
PlyData *data)
|
||||
{
|
||||
/* Figure out vertex component indices. */
|
||||
int3 vertex_index = {get_index(element, "x"), get_index(element, "y"), get_index(element, "z")};
|
||||
int3 color_index = {
|
||||
get_index(element, "red"), get_index(element, "green"), get_index(element, "blue")};
|
||||
int3 normal_index = {
|
||||
get_index(element, "nx"), get_index(element, "ny"), get_index(element, "nz")};
|
||||
int2 uv_index = {get_index(element, "s"), get_index(element, "t")};
|
||||
int alpha_index = get_index(element, "alpha");
|
||||
|
||||
bool has_vertex = vertex_index.x >= 0 && vertex_index.y >= 0 && vertex_index.z >= 0;
|
||||
bool has_color = color_index.x >= 0 && color_index.y >= 0 && color_index.z >= 0;
|
||||
bool has_normal = normal_index.x >= 0 && normal_index.y >= 0 && normal_index.z >= 0;
|
||||
bool has_uv = uv_index.x >= 0 && uv_index.y >= 0;
|
||||
bool has_alpha = alpha_index >= 0;
|
||||
|
||||
if (!has_vertex) {
|
||||
return "Vertex positions are not present in the file";
|
||||
}
|
||||
|
||||
Vector<int64_t> custom_attr_indices;
|
||||
for (const int64_t prop_idx : element.properties.index_range()) {
|
||||
const PlyProperty &prop = element.properties[prop_idx];
|
||||
bool is_standard = ELEM(
|
||||
prop.name, "x", "y", "z", "nx", "ny", "nz", "red", "green", "blue", "alpha", "s", "t");
|
||||
if (is_standard) {
|
||||
continue;
|
||||
}
|
||||
|
||||
custom_attr_indices.append(prop_idx);
|
||||
PlyCustomAttribute attr(prop.name, element.count);
|
||||
data->vertex_custom_attr.append(attr);
|
||||
}
|
||||
|
||||
data->vertices.reserve(element.count);
|
||||
if (has_color) {
|
||||
data->vertex_colors.reserve(element.count);
|
||||
}
|
||||
if (has_normal) {
|
||||
data->vertex_normals.reserve(element.count);
|
||||
}
|
||||
if (has_uv) {
|
||||
data->uv_coordinates.reserve(element.count);
|
||||
}
|
||||
|
||||
float4 color_norm = {1, 1, 1, 1};
|
||||
if (has_color) {
|
||||
color_norm.x = data_type_normalizer[element.properties[color_index.x].type];
|
||||
color_norm.y = data_type_normalizer[element.properties[color_index.y].type];
|
||||
color_norm.z = data_type_normalizer[element.properties[color_index.z].type];
|
||||
}
|
||||
if (has_alpha) {
|
||||
color_norm.w = data_type_normalizer[element.properties[alpha_index].type];
|
||||
}
|
||||
|
||||
Vector<float> value_vec(element.properties.size());
|
||||
Vector<uint8_t> scratch;
|
||||
if (header.type != PlyFormatType::ASCII) {
|
||||
scratch.resize(element.stride);
|
||||
}
|
||||
|
||||
for (int i = 0; i < element.count; i++) {
|
||||
|
||||
const char *error = nullptr;
|
||||
if (header.type == PlyFormatType::ASCII) {
|
||||
error = parse_row_ascii(file, value_vec);
|
||||
}
|
||||
else {
|
||||
error = parse_row_binary(file, header, element, scratch, value_vec);
|
||||
}
|
||||
if (error != nullptr) {
|
||||
return error;
|
||||
}
|
||||
|
||||
/* Vertex coord */
|
||||
float3 vertex3;
|
||||
vertex3.x = value_vec[vertex_index.x];
|
||||
vertex3.y = value_vec[vertex_index.y];
|
||||
vertex3.z = value_vec[vertex_index.z];
|
||||
data->vertices.append(vertex3);
|
||||
|
||||
/* Vertex color */
|
||||
if (has_color) {
|
||||
float4 colors4;
|
||||
colors4.x = value_vec[color_index.x] / color_norm.x;
|
||||
colors4.y = value_vec[color_index.y] / color_norm.y;
|
||||
colors4.z = value_vec[color_index.z] / color_norm.z;
|
||||
if (has_alpha) {
|
||||
colors4.w = value_vec[alpha_index] / color_norm.w;
|
||||
}
|
||||
else {
|
||||
colors4.w = 1.0f;
|
||||
}
|
||||
data->vertex_colors.append(colors4);
|
||||
}
|
||||
|
||||
/* If normals */
|
||||
if (has_normal) {
|
||||
float3 normals3;
|
||||
normals3.x = value_vec[normal_index.x];
|
||||
normals3.y = value_vec[normal_index.y];
|
||||
normals3.z = value_vec[normal_index.z];
|
||||
data->vertex_normals.append(normals3);
|
||||
}
|
||||
|
||||
/* If uv */
|
||||
if (has_uv) {
|
||||
float2 uvmap;
|
||||
uvmap.x = value_vec[uv_index.x];
|
||||
uvmap.y = value_vec[uv_index.y];
|
||||
data->uv_coordinates.append(uvmap);
|
||||
}
|
||||
|
||||
/* Custom attributes */
|
||||
for (const int64_t ci : custom_attr_indices.index_range()) {
|
||||
float value = value_vec[custom_attr_indices[ci]];
|
||||
data->vertex_custom_attr[ci].data[i] = value;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static uint32_t read_list_count(PlyReadBuffer &file,
|
||||
const PlyProperty &prop,
|
||||
Vector<uint8_t> &scratch,
|
||||
bool big_endian)
|
||||
{
|
||||
scratch.resize(8);
|
||||
file.read_bytes(scratch.data(), data_type_size[prop.count_type]);
|
||||
const uint8_t *ptr = scratch.data();
|
||||
if (big_endian) {
|
||||
endian_switch(const_cast<uint8_t *>(ptr), data_type_size[prop.count_type]);
|
||||
}
|
||||
uint32_t count = get_binary_value<uint32_t>(prop.count_type, ptr);
|
||||
return count;
|
||||
}
|
||||
|
||||
static void skip_property(PlyReadBuffer &file,
|
||||
const PlyProperty &prop,
|
||||
Vector<uint8_t> &scratch,
|
||||
bool big_endian)
|
||||
{
|
||||
if (prop.count_type == PlyDataTypes::NONE) {
|
||||
scratch.resize(8);
|
||||
file.read_bytes(scratch.data(), data_type_size[prop.type]);
|
||||
}
|
||||
else {
|
||||
uint32_t count = read_list_count(file, prop, scratch, big_endian);
|
||||
scratch.resize(count * data_type_size[prop.type]);
|
||||
file.read_bytes(scratch.data(), scratch.size());
|
||||
}
|
||||
}
|
||||
|
||||
static const char *load_face_element(PlyReadBuffer &file,
|
||||
const PlyHeader &header,
|
||||
const PlyElement &element,
|
||||
PlyData *data)
|
||||
{
|
||||
int prop_index = get_index(element, "vertex_indices");
|
||||
if (prop_index < 0) {
|
||||
prop_index = get_index(element, "vertex_index");
|
||||
}
|
||||
if (prop_index < 0 && element.properties.size() == 1) {
|
||||
prop_index = 0;
|
||||
}
|
||||
if (prop_index < 0) {
|
||||
return "Face element does not contain vertex indices property";
|
||||
}
|
||||
const PlyProperty &prop = element.properties[prop_index];
|
||||
if (prop.count_type == PlyDataTypes::NONE) {
|
||||
return "Face element vertex indices property must be a list";
|
||||
}
|
||||
|
||||
data->face_vertices.reserve(int64_t(element.count) * 3);
|
||||
data->face_sizes.reserve(element.count);
|
||||
|
||||
if (header.type == PlyFormatType::ASCII) {
|
||||
for (int i = 0; i < element.count; i++) {
|
||||
/* Read line */
|
||||
Span<char> line = file.read_line();
|
||||
|
||||
const char *p = line.data();
|
||||
const char *end = p + line.size();
|
||||
int count = 0;
|
||||
|
||||
/* Skip any properties before vertex indices. */
|
||||
for (int j = 0; j < prop_index; j++) {
|
||||
p = drop_whitespace(p, end);
|
||||
if (element.properties[j].count_type == PlyDataTypes::NONE) {
|
||||
p = drop_non_whitespace(p, end);
|
||||
}
|
||||
else {
|
||||
p = parse_int(p, end, 0, count);
|
||||
for (int k = 0; k < count; ++k) {
|
||||
p = drop_whitespace(p, end);
|
||||
p = drop_non_whitespace(p, end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Parse vertex indices list. */
|
||||
p = parse_int(p, end, 0, count);
|
||||
if (count < 1 || count > 255) {
|
||||
return "Invalid face size, must be between 1 and 255";
|
||||
}
|
||||
/* Previous python based importer was accepting faces with fewer
|
||||
* than 3 vertices, and silently dropping them. */
|
||||
if (count < 3) {
|
||||
CLOG_WARN(&LOG, "PLY Importer: ignoring face %i (%i vertices)", i, count);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int j = 0; j < count; j++) {
|
||||
int index;
|
||||
p = parse_int(p, end, 0, index);
|
||||
data->face_vertices.append(index);
|
||||
}
|
||||
data->face_sizes.append(count);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Vector<uint8_t> scratch(64);
|
||||
|
||||
for (int i = 0; i < element.count; i++) {
|
||||
const uint8_t *ptr;
|
||||
|
||||
/* Skip any properties before vertex indices. */
|
||||
for (int j = 0; j < prop_index; j++) {
|
||||
skip_property(
|
||||
file, element.properties[j], scratch, header.type == PlyFormatType::BINARY_BE);
|
||||
}
|
||||
|
||||
/* Read vertex indices list. */
|
||||
uint32_t count = read_list_count(
|
||||
file, prop, scratch, header.type == PlyFormatType::BINARY_BE);
|
||||
if (count < 1 || count > 255) {
|
||||
return "Invalid face size, must be between 1 and 255";
|
||||
}
|
||||
|
||||
scratch.resize(count * data_type_size[prop.type]);
|
||||
file.read_bytes(scratch.data(), scratch.size());
|
||||
/* Previous python based importer was accepting faces with fewer
|
||||
* than 3 vertices, and silently dropping them. */
|
||||
if (count < 3) {
|
||||
CLOG_WARN(&LOG, "PLY Importer: ignoring face %i (%u vertices)", i, count);
|
||||
}
|
||||
else {
|
||||
ptr = scratch.data();
|
||||
if (header.type == PlyFormatType::BINARY_BE) {
|
||||
endian_switch_array(const_cast<uint8_t *>(ptr), data_type_size[prop.type], count);
|
||||
}
|
||||
for (int j = 0; j < count; ++j) {
|
||||
uint32_t index = get_binary_value<uint32_t>(prop.type, ptr);
|
||||
data->face_vertices.append(index);
|
||||
}
|
||||
data->face_sizes.append(count);
|
||||
}
|
||||
|
||||
/* Skip any properties after vertex indices. */
|
||||
for (int j = prop_index + 1; j < element.properties.size(); j++) {
|
||||
skip_property(
|
||||
file, element.properties[j], scratch, header.type == PlyFormatType::BINARY_BE);
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static const char *load_tristrips_element(PlyReadBuffer &file,
|
||||
const PlyHeader &header,
|
||||
const PlyElement &element,
|
||||
PlyData *data)
|
||||
{
|
||||
if (element.count != 1) {
|
||||
return "Tristrips element should contain one row";
|
||||
}
|
||||
if (element.properties.size() != 1) {
|
||||
return "Tristrips element should contain one property";
|
||||
}
|
||||
const PlyProperty &prop = element.properties[0];
|
||||
if (prop.count_type == PlyDataTypes::NONE) {
|
||||
return "Tristrips element property must be a list";
|
||||
}
|
||||
|
||||
Vector<int> strip;
|
||||
|
||||
if (header.type == PlyFormatType::ASCII) {
|
||||
Span<char> line = file.read_line();
|
||||
|
||||
const char *p = line.data();
|
||||
const char *end = p + line.size();
|
||||
int count = 0;
|
||||
p = parse_int(p, end, 0, count);
|
||||
|
||||
strip.resize(count);
|
||||
for (int j = 0; j < count; j++) {
|
||||
int index;
|
||||
p = parse_int(p, end, 0, index);
|
||||
strip[j] = index;
|
||||
}
|
||||
}
|
||||
else {
|
||||
Vector<uint8_t> scratch(64);
|
||||
|
||||
const uint8_t *ptr;
|
||||
|
||||
uint32_t count = read_list_count(file, prop, scratch, header.type == PlyFormatType::BINARY_BE);
|
||||
|
||||
strip.resize(count);
|
||||
scratch.resize(count * data_type_size[prop.type]);
|
||||
file.read_bytes(scratch.data(), scratch.size());
|
||||
ptr = scratch.data();
|
||||
if (header.type == PlyFormatType::BINARY_BE) {
|
||||
endian_switch_array(const_cast<uint8_t *>(ptr), data_type_size[prop.type], count);
|
||||
}
|
||||
for (int j = 0; j < count; ++j) {
|
||||
int index = get_binary_value<int>(prop.type, ptr);
|
||||
strip[j] = index;
|
||||
}
|
||||
}
|
||||
|
||||
/* Decode triangle strip (with possible -1 restart indices) into faces. */
|
||||
size_t start = 0;
|
||||
|
||||
for (size_t i = 0; i < strip.size(); i++) {
|
||||
if (strip[i] == -1) {
|
||||
/* Restart strip. */
|
||||
start = i + 1;
|
||||
}
|
||||
else if (i - start >= 2) {
|
||||
int a = strip[i - 2], b = strip[i - 1], c = strip[i];
|
||||
/* Flip odd triangles. */
|
||||
if ((i - start) & 1) {
|
||||
std::swap(a, b);
|
||||
}
|
||||
/* Add triangle if it's not degenerate. */
|
||||
if (a != b && a != c && b != c) {
|
||||
data->face_vertices.append(a);
|
||||
data->face_vertices.append(b);
|
||||
data->face_vertices.append(c);
|
||||
data->face_sizes.append(3);
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static const char *load_edge_element(PlyReadBuffer &file,
|
||||
const PlyHeader &header,
|
||||
const PlyElement &element,
|
||||
PlyData *data)
|
||||
{
|
||||
int prop_vertex1 = get_index(element, "vertex1");
|
||||
int prop_vertex2 = get_index(element, "vertex2");
|
||||
if (prop_vertex1 < 0 || prop_vertex2 < 0) {
|
||||
return "Edge element does not contain vertex1 and vertex2 properties";
|
||||
}
|
||||
|
||||
data->edges.reserve(element.count);
|
||||
|
||||
Vector<float> value_vec(element.properties.size());
|
||||
Vector<uint8_t> scratch;
|
||||
if (header.type != PlyFormatType::ASCII) {
|
||||
scratch.resize(element.stride);
|
||||
}
|
||||
|
||||
for (int i = 0; i < element.count; i++) {
|
||||
const char *error = nullptr;
|
||||
if (header.type == PlyFormatType::ASCII) {
|
||||
error = parse_row_ascii(file, value_vec);
|
||||
}
|
||||
else {
|
||||
error = parse_row_binary(file, header, element, scratch, value_vec);
|
||||
}
|
||||
if (error != nullptr) {
|
||||
return error;
|
||||
}
|
||||
int index1 = value_vec[prop_vertex1];
|
||||
int index2 = value_vec[prop_vertex2];
|
||||
data->edges.append(std::make_pair(index1, index2));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static const char *skip_element(PlyReadBuffer &file,
|
||||
const PlyHeader &header,
|
||||
const PlyElement &element)
|
||||
{
|
||||
if (header.type == PlyFormatType::ASCII) {
|
||||
for (int i = 0; i < element.count; i++) {
|
||||
Span<char> line = file.read_line();
|
||||
(void)line;
|
||||
}
|
||||
}
|
||||
else {
|
||||
Vector<uint8_t> scratch(64);
|
||||
for (int i = 0; i < element.count; i++) {
|
||||
for (const PlyProperty &prop : element.properties) {
|
||||
skip_property(file, prop, scratch, header.type == PlyFormatType::BINARY_BE);
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<PlyData> import_ply_data(PlyReadBuffer &file, PlyHeader &header)
|
||||
{
|
||||
std::unique_ptr<PlyData> data = std::make_unique<PlyData>();
|
||||
|
||||
bool got_vertex = false, got_face = false, got_tristrips = false, got_edge = false;
|
||||
for (const PlyElement &element : header.elements) {
|
||||
const char *error = nullptr;
|
||||
if (element.name == "vertex") {
|
||||
error = load_vertex_element(file, header, element, data.get());
|
||||
got_vertex = true;
|
||||
}
|
||||
else if (element.name == "face") {
|
||||
error = load_face_element(file, header, element, data.get());
|
||||
got_face = true;
|
||||
}
|
||||
else if (element.name == "tristrips") {
|
||||
error = load_tristrips_element(file, header, element, data.get());
|
||||
got_tristrips = true;
|
||||
}
|
||||
else if (element.name == "edge") {
|
||||
error = load_edge_element(file, header, element, data.get());
|
||||
got_edge = true;
|
||||
}
|
||||
else {
|
||||
error = skip_element(file, header, element);
|
||||
}
|
||||
if (error != nullptr) {
|
||||
data->error = error;
|
||||
return data;
|
||||
}
|
||||
if (got_vertex && got_face && got_tristrips && got_edge) {
|
||||
/* We have parsed all the elements we'd need, skip the rest. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
} // namespace io::ply
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,25 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ply_data.hh"
|
||||
|
||||
namespace blender::io::ply {
|
||||
|
||||
class PlyReadBuffer;
|
||||
|
||||
/**
|
||||
* Loads the information from a PLY file to a #PlyData data-structure.
|
||||
* \param file: The PLY file that was opened.
|
||||
* \param header: The information in the PLY header.
|
||||
* \return The #PlyData data-structure that can be used for conversion to a Mesh.
|
||||
*/
|
||||
std::unique_ptr<PlyData> import_ply_data(PlyReadBuffer &file, PlyHeader &header);
|
||||
|
||||
} // namespace blender::io::ply
|
||||
187
blender-5.2.0/source/blender/io/ply/importer/ply_import_mesh.cc
Normal file
187
blender-5.2.0/source/blender/io/ply/importer/ply_import_mesh.cc
Normal file
@@ -0,0 +1,187 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#include "BKE_attribute.h"
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_lib_id.hh"
|
||||
#include "BKE_mesh.hh"
|
||||
|
||||
#include "GEO_mesh_merge_verts.hh"
|
||||
|
||||
#include "BLI_color_types.hh"
|
||||
#include "BLI_math_color.h"
|
||||
#include "BLI_math_vector.h"
|
||||
#include "BLI_span.hh"
|
||||
|
||||
#include "IO_validate.hh"
|
||||
|
||||
#include "ply_import_mesh.hh"
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
namespace blender {
|
||||
|
||||
static CLG_LogRef LOG = {"io.ply"};
|
||||
|
||||
namespace io::ply {
|
||||
Mesh *convert_ply_to_mesh(PlyData &data, const PLYImportParams ¶ms)
|
||||
{
|
||||
if (!validate::size_fits_in_int(data.vertices.size()) ||
|
||||
!validate::size_fits_in_int(data.edges.size()) ||
|
||||
!validate::size_fits_in_int(data.face_sizes.size()) ||
|
||||
!validate::size_fits_in_int(data.face_vertices.size()))
|
||||
{
|
||||
CLOG_WARN(&LOG, "PLY mesh too large to import, exceeds max int size");
|
||||
return BKE_mesh_new_nomain(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
Mesh *mesh = BKE_mesh_new_nomain(
|
||||
data.vertices.size(), data.edges.size(), data.face_sizes.size(), data.face_vertices.size());
|
||||
|
||||
mesh->vert_positions_for_write().copy_from(data.vertices);
|
||||
|
||||
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
|
||||
|
||||
if (!data.edges.is_empty()) {
|
||||
MutableSpan<int2> edges = mesh->edges_for_write();
|
||||
for (const int i : data.edges.index_range()) {
|
||||
int32_t v1 = data.edges[i].first;
|
||||
int32_t v2 = data.edges[i].second;
|
||||
if (!validate::index_in_range(v1, mesh->verts_num)) {
|
||||
CLOG_WARN(&LOG, "Invalid PLY vertex index in edge %i/1: %d", i, v1);
|
||||
v1 = 0;
|
||||
}
|
||||
if (!validate::index_in_range(v2, mesh->verts_num)) {
|
||||
CLOG_WARN(&LOG, "Invalid PLY vertex index in edge %i/2: %d", i, v2);
|
||||
v2 = 0;
|
||||
}
|
||||
edges[i] = {v1, v2};
|
||||
}
|
||||
}
|
||||
|
||||
/* Add faces to the mesh. */
|
||||
if (!data.face_sizes.is_empty()) {
|
||||
MutableSpan<int> face_offsets = mesh->face_offsets_for_write();
|
||||
MutableSpan<int> corner_verts = mesh->corner_verts_for_write();
|
||||
|
||||
/* Fill in face data. */
|
||||
int64_t offset = 0;
|
||||
for (const int64_t i : data.face_sizes.index_range()) {
|
||||
const int64_t size = data.face_sizes[i];
|
||||
face_offsets[i] = offset;
|
||||
for (int64_t j = 0; j < size; j++) {
|
||||
uint32_t v = data.face_vertices[offset + j];
|
||||
if (!validate::index_in_range(v, mesh->verts_num)) {
|
||||
CLOG_WARN(
|
||||
&LOG, "Invalid PLY vertex index in face %" PRId64 " loop %" PRId64 ": %u", i, j, v);
|
||||
v = 0;
|
||||
}
|
||||
corner_verts[offset + j] = v;
|
||||
}
|
||||
offset += size;
|
||||
}
|
||||
}
|
||||
|
||||
/* Vertex colors */
|
||||
if (!data.vertex_colors.is_empty() && params.vertex_colors != ePLYVertexColorMode::None) {
|
||||
/* Create a data layer for vertex colors and set them. */
|
||||
bke::SpanAttributeWriter colors = attributes.lookup_or_add_for_write_span<ColorGeometry4f>(
|
||||
"Col", bke::AttrDomain::Point);
|
||||
|
||||
if (params.vertex_colors == ePLYVertexColorMode::sRGB) {
|
||||
for (const int i : data.vertex_colors.index_range()) {
|
||||
srgb_to_linearrgb_v4(colors.span[i], data.vertex_colors[i]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (const int i : data.vertex_colors.index_range()) {
|
||||
copy_v4_v4(colors.span[i], data.vertex_colors[i]);
|
||||
}
|
||||
}
|
||||
colors.finish();
|
||||
BKE_id_attributes_active_color_set(&mesh->id, "Col");
|
||||
BKE_id_attributes_default_color_set(&mesh->id, "Col");
|
||||
}
|
||||
|
||||
/* Uvmap */
|
||||
if (!data.uv_coordinates.is_empty()) {
|
||||
bke::SpanAttributeWriter<float2> uv_map = attributes.lookup_or_add_for_write_only_span<float2>(
|
||||
"UVMap", bke::AttrDomain::Corner);
|
||||
for (const int i : data.face_vertices.index_range()) {
|
||||
uint32_t v = data.face_vertices[i];
|
||||
uv_map.span[i] = validate::index_in_range(v, data.uv_coordinates.size()) ?
|
||||
data.uv_coordinates[v] :
|
||||
float2(0.0f);
|
||||
}
|
||||
uv_map.finish();
|
||||
mesh->uv_maps_active_set("UVMap");
|
||||
mesh->uv_maps_default_set("UVMap");
|
||||
}
|
||||
|
||||
/* If we have custom vertex normals, set them
|
||||
* (NOTE: important to do this after initializing the loops). */
|
||||
bool set_custom_normals_for_verts = false;
|
||||
if (!data.vertex_normals.is_empty()) {
|
||||
if (!data.face_sizes.is_empty()) {
|
||||
/* For a non-point-cloud mesh, set custom normals. */
|
||||
/* Deferred because this relies on valid mesh data. */
|
||||
set_custom_normals_for_verts = true;
|
||||
}
|
||||
else if (params.import_attributes) {
|
||||
/* If we have no faces, add vertex normals as custom attribute. */
|
||||
attributes.add<float3>(
|
||||
"normal",
|
||||
bke::AttrDomain::Point,
|
||||
bke::AttributeInitVArray(VArray<float3>::from_span(data.vertex_normals)));
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* No vertex normals: set faces to sharp. */
|
||||
bke::mesh_smooth_set(*mesh, false);
|
||||
}
|
||||
|
||||
/* Custom attributes: add them after anything above. */
|
||||
if (params.import_attributes && !data.vertex_custom_attr.is_empty()) {
|
||||
for (const PlyCustomAttribute &attr : data.vertex_custom_attr) {
|
||||
attributes.add<float>(attr.name,
|
||||
bke::AttrDomain::Point,
|
||||
bke::AttributeInitVArray(VArray<float>::from_span(attr.data)));
|
||||
}
|
||||
}
|
||||
|
||||
/* It's important to validate the mesh before using it's geometry to calculate derived data. */
|
||||
{
|
||||
const bool allow_missing_edges = true;
|
||||
#ifndef NDEBUG
|
||||
const bool verbose_validate = true;
|
||||
#else
|
||||
const bool verbose_validate = false;
|
||||
#endif
|
||||
bke::mesh_validate(*mesh, verbose_validate, allow_missing_edges);
|
||||
}
|
||||
|
||||
if (set_custom_normals_for_verts) {
|
||||
bke::mesh_set_custom_normals_from_verts(*mesh, data.vertex_normals);
|
||||
}
|
||||
|
||||
/* Merge all vertices on the same location. */
|
||||
if (params.merge_verts) {
|
||||
std::optional<Mesh *> merged_mesh = geometry::mesh_merge_by_distance_all(
|
||||
*mesh, IndexMask(mesh->verts_num), 0.0001f);
|
||||
if (merged_mesh) {
|
||||
BKE_id_free(nullptr, &mesh->id);
|
||||
mesh = *merged_mesh;
|
||||
}
|
||||
}
|
||||
|
||||
return mesh;
|
||||
}
|
||||
} // namespace io::ply
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,27 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IO_ply.hh"
|
||||
#include "ply_data.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Mesh;
|
||||
|
||||
namespace io::ply {
|
||||
|
||||
/**
|
||||
* Converts the #PlyData data-structure to a mesh.
|
||||
* \return A new mesh that can be used inside blender.
|
||||
*/
|
||||
Mesh *convert_ply_to_mesh(PlyData &data, const PLYImportParams ¶ms);
|
||||
|
||||
} // namespace io::ply
|
||||
} // namespace blender
|
||||
62
blender-5.2.0/source/blender/io/ply/intern/ply_data.hh
Normal file
62
blender-5.2.0/source/blender/io/ply/intern/ply_data.hh
Normal file
@@ -0,0 +1,62 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "BLI_math_vector_types.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
namespace blender::io::ply {
|
||||
|
||||
enum PlyDataTypes { NONE, CHAR, UCHAR, SHORT, USHORT, INT, UINT, FLOAT, DOUBLE, PLY_TYPE_COUNT };
|
||||
|
||||
struct PlyCustomAttribute {
|
||||
PlyCustomAttribute(const StringRef name_, int64_t size) : name(name_), data(size, 0.0f) {}
|
||||
std::string name;
|
||||
Vector<float> data; /* Any custom PLY attributes are converted to floats. */
|
||||
};
|
||||
|
||||
struct PlyData {
|
||||
Vector<float3> vertices;
|
||||
Vector<float3> vertex_normals;
|
||||
Vector<float4> vertex_colors; /* Linear space, 0..1 range colors. */
|
||||
Vector<PlyCustomAttribute> vertex_custom_attr;
|
||||
Vector<std::pair<int, int>> edges;
|
||||
Vector<uint32_t> face_vertices;
|
||||
Vector<uint32_t> face_sizes;
|
||||
Vector<float2> uv_coordinates;
|
||||
std::string error;
|
||||
};
|
||||
|
||||
enum PlyFormatType { ASCII, BINARY_LE, BINARY_BE };
|
||||
|
||||
struct PlyProperty {
|
||||
std::string name;
|
||||
PlyDataTypes type = PlyDataTypes::NONE;
|
||||
PlyDataTypes count_type = PlyDataTypes::NONE; /* NONE means it's not a list property */
|
||||
};
|
||||
|
||||
struct PlyElement {
|
||||
std::string name;
|
||||
int count = 0;
|
||||
Vector<PlyProperty> properties;
|
||||
int stride = 0;
|
||||
|
||||
void calc_stride();
|
||||
};
|
||||
|
||||
struct PlyHeader {
|
||||
Vector<PlyElement> elements;
|
||||
PlyFormatType type;
|
||||
};
|
||||
|
||||
} // namespace blender::io::ply
|
||||
@@ -0,0 +1,552 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "testing/testing.h"
|
||||
#include "tests/blendfile_loading_base_test.h"
|
||||
|
||||
#include "BLI_fileops.h"
|
||||
#include "BLI_string.h"
|
||||
|
||||
#include "BKE_appdir.hh"
|
||||
#include "BKE_blender_version.h"
|
||||
|
||||
#include "DEG_depsgraph.hh"
|
||||
|
||||
#include "IO_ply.hh"
|
||||
#include "intern/ply_data.hh"
|
||||
|
||||
#include "ply_export_data.hh"
|
||||
#include "ply_export_header.hh"
|
||||
#include "ply_export_load_plydata.hh"
|
||||
#include "ply_file_buffer_ascii.hh"
|
||||
#include "ply_file_buffer_binary.hh"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
namespace blender::io::ply {
|
||||
|
||||
class PLYExportTest : public BlendfileLoadingBaseTest {
|
||||
public:
|
||||
bool load_file_and_depsgraph(const std::string &filepath,
|
||||
const eEvaluationMode eval_mode = DAG_EVAL_VIEWPORT)
|
||||
{
|
||||
if (!blendfile_load(filepath.c_str())) {
|
||||
return false;
|
||||
}
|
||||
depsgraph_create(eval_mode);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
void SetUp() override
|
||||
{
|
||||
BlendfileLoadingBaseTest::SetUp();
|
||||
|
||||
BKE_tempdir_init(nullptr);
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
BlendfileLoadingBaseTest::TearDown();
|
||||
|
||||
BKE_tempdir_session_purge();
|
||||
}
|
||||
|
||||
std::string get_temp_ply_filename(const std::string &filename)
|
||||
{
|
||||
return std::string(BKE_tempdir_session()) + SEP_STR + filename;
|
||||
}
|
||||
};
|
||||
|
||||
static std::unique_ptr<PlyData> load_cube(PLYExportParams ¶ms)
|
||||
{
|
||||
std::unique_ptr<PlyData> plyData = std::make_unique<PlyData>();
|
||||
plyData->vertices = {
|
||||
{1.122082, 1.122082, 1.122082},
|
||||
{1.122082, 1.122082, -1.122082},
|
||||
{1.122082, -1.122082, 1.122082},
|
||||
{1.122082, -1.122082, -1.122082},
|
||||
{-1.122082, 1.122082, 1.122082},
|
||||
{-1.122082, 1.122082, -1.122082},
|
||||
{-1.122082, -1.122082, 1.122082},
|
||||
{-1.122082, -1.122082, -1.122082},
|
||||
};
|
||||
|
||||
plyData->face_sizes = {4, 4, 4, 4, 4, 4};
|
||||
plyData->face_vertices = {0, 2, 6, 4, 3, 7, 6, 2, 7, 5, 4, 6,
|
||||
5, 7, 3, 1, 1, 3, 2, 0, 5, 1, 0, 4};
|
||||
|
||||
if (params.export_normals) {
|
||||
plyData->vertex_normals = {
|
||||
{-0.5773503, -0.5773503, -0.5773503},
|
||||
{-0.5773503, -0.5773503, 0.5773503},
|
||||
{-0.5773503, 0.5773503, -0.5773503},
|
||||
{-0.5773503, 0.5773503, 0.5773503},
|
||||
{0.5773503, -0.5773503, -0.5773503},
|
||||
{0.5773503, -0.5773503, 0.5773503},
|
||||
{0.5773503, 0.5773503, -0.5773503},
|
||||
{0.5773503, 0.5773503, 0.5773503},
|
||||
};
|
||||
}
|
||||
|
||||
return plyData;
|
||||
}
|
||||
|
||||
/* The following is relative to BKE_tempdir_base.
|
||||
* Use Latin Capital Letter A with Ogonek, Cyrillic Capital Letter Zhe
|
||||
* at the end, to test I/O on non-English file names. */
|
||||
const char *const temp_file_path = "output\xc4\x84\xd0\x96.ply";
|
||||
|
||||
static std::string read_temp_file_in_string(const std::string &file_path)
|
||||
{
|
||||
std::string res;
|
||||
size_t buffer_len;
|
||||
char *buffer = BLI_file_read_text_as_mem(file_path.c_str(), 0, &buffer_len);
|
||||
if (buffer != nullptr) {
|
||||
res.assign(buffer, buffer_len);
|
||||
MEM_delete(buffer);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static char read(std::ifstream &file)
|
||||
{
|
||||
char return_val;
|
||||
file.read(&return_val, sizeof(return_val));
|
||||
return return_val;
|
||||
}
|
||||
|
||||
static std::vector<char> read_temp_file_in_vectorchar(const std::string &file_path)
|
||||
{
|
||||
std::vector<char> res;
|
||||
std::ifstream infile(file_path, std::ios::binary);
|
||||
while (true) {
|
||||
uint64_t c = read(infile);
|
||||
if (!infile.eof()) {
|
||||
res.push_back(c);
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
TEST_F(PLYExportTest, WriteHeaderAscii)
|
||||
{
|
||||
std::string filePath = get_temp_ply_filename(temp_file_path);
|
||||
PLYExportParams _params;
|
||||
_params.ascii_format = true;
|
||||
_params.export_normals = false;
|
||||
_params.vertex_colors = ePLYVertexColorMode::None;
|
||||
STRNCPY(_params.filepath, filePath.c_str());
|
||||
|
||||
std::unique_ptr<PlyData> plyData = load_cube(_params);
|
||||
|
||||
std::unique_ptr<FileBuffer> buffer = std::make_unique<FileBufferAscii>(_params.filepath);
|
||||
|
||||
write_header(*buffer, *plyData, _params);
|
||||
|
||||
buffer->close_file();
|
||||
|
||||
std::string result = read_temp_file_in_string(filePath);
|
||||
|
||||
StringRef version = BKE_blender_version_string();
|
||||
|
||||
std::string expected =
|
||||
"ply\n"
|
||||
"format ascii 1.0\n"
|
||||
"comment Created in Blender version " +
|
||||
version +
|
||||
"\n"
|
||||
"element vertex 8\n"
|
||||
"property float x\n"
|
||||
"property float y\n"
|
||||
"property float z\n"
|
||||
"element face 6\n"
|
||||
"property list uchar uint vertex_indices\n"
|
||||
"end_header\n";
|
||||
|
||||
ASSERT_STREQ(result.c_str(), expected.c_str());
|
||||
}
|
||||
|
||||
TEST_F(PLYExportTest, WriteHeaderBinary)
|
||||
{
|
||||
std::string filePath = get_temp_ply_filename(temp_file_path);
|
||||
PLYExportParams _params;
|
||||
_params.ascii_format = false;
|
||||
_params.export_normals = false;
|
||||
_params.vertex_colors = ePLYVertexColorMode::None;
|
||||
STRNCPY(_params.filepath, filePath.c_str());
|
||||
|
||||
std::unique_ptr<PlyData> plyData = load_cube(_params);
|
||||
|
||||
std::unique_ptr<FileBuffer> buffer = std::make_unique<FileBufferBinary>(_params.filepath);
|
||||
|
||||
write_header(*buffer, *plyData, _params);
|
||||
|
||||
buffer->close_file();
|
||||
|
||||
std::string result = read_temp_file_in_string(filePath);
|
||||
|
||||
StringRef version = BKE_blender_version_string();
|
||||
|
||||
std::string expected =
|
||||
"ply\n"
|
||||
"format binary_little_endian 1.0\n"
|
||||
"comment Created in Blender version " +
|
||||
version +
|
||||
"\n"
|
||||
"element vertex 8\n"
|
||||
"property float x\n"
|
||||
"property float y\n"
|
||||
"property float z\n"
|
||||
"element face 6\n"
|
||||
"property list uchar uint vertex_indices\n"
|
||||
"end_header\n";
|
||||
|
||||
ASSERT_STREQ(result.c_str(), expected.c_str());
|
||||
}
|
||||
|
||||
TEST_F(PLYExportTest, WriteVerticesAscii)
|
||||
{
|
||||
std::string filePath = get_temp_ply_filename(temp_file_path);
|
||||
PLYExportParams _params;
|
||||
_params.ascii_format = true;
|
||||
_params.export_normals = false;
|
||||
_params.vertex_colors = ePLYVertexColorMode::None;
|
||||
STRNCPY(_params.filepath, filePath.c_str());
|
||||
|
||||
std::unique_ptr<PlyData> plyData = load_cube(_params);
|
||||
|
||||
std::unique_ptr<FileBuffer> buffer = std::make_unique<FileBufferAscii>(_params.filepath);
|
||||
|
||||
write_vertices(*buffer, *plyData);
|
||||
|
||||
buffer->close_file();
|
||||
|
||||
std::string result = read_temp_file_in_string(filePath);
|
||||
|
||||
std::string expected =
|
||||
"1.122082 1.122082 1.122082\n"
|
||||
"1.122082 1.122082 -1.122082\n"
|
||||
"1.122082 -1.122082 1.122082\n"
|
||||
"1.122082 -1.122082 -1.122082\n"
|
||||
"-1.122082 1.122082 1.122082\n"
|
||||
"-1.122082 1.122082 -1.122082\n"
|
||||
"-1.122082 -1.122082 1.122082\n"
|
||||
"-1.122082 -1.122082 -1.122082\n";
|
||||
|
||||
ASSERT_STREQ(result.c_str(), expected.c_str());
|
||||
}
|
||||
|
||||
TEST_F(PLYExportTest, WriteVerticesBinary)
|
||||
{
|
||||
std::string filePath = get_temp_ply_filename(temp_file_path);
|
||||
PLYExportParams _params;
|
||||
_params.ascii_format = false;
|
||||
_params.export_normals = false;
|
||||
_params.vertex_colors = ePLYVertexColorMode::None;
|
||||
STRNCPY(_params.filepath, filePath.c_str());
|
||||
|
||||
std::unique_ptr<PlyData> plyData = load_cube(_params);
|
||||
|
||||
std::unique_ptr<FileBuffer> buffer = std::make_unique<FileBufferBinary>(_params.filepath);
|
||||
|
||||
write_vertices(*buffer, *plyData);
|
||||
|
||||
buffer->close_file();
|
||||
|
||||
std::vector<char> result = read_temp_file_in_vectorchar(filePath);
|
||||
|
||||
std::vector<char> expected({
|
||||
0x62, 0xA0, 0x8F, 0x3F, 0x62, 0xA0, 0x8F, 0x3F, 0x62, 0xA0, 0x8F, 0x3F, 0x62, 0xA0,
|
||||
0x8F, 0x3F, 0x62, 0xA0, 0x8F, 0x3F, 0x62, 0xA0, 0x8F, 0xBF, 0x62, 0xA0, 0x8F, 0x3F,
|
||||
0x62, 0xA0, 0x8F, 0xBF, 0x62, 0xA0, 0x8F, 0x3F, 0x62, 0xA0, 0x8F, 0x3F, 0x62, 0xA0,
|
||||
0x8F, 0xBF, 0x62, 0xA0, 0x8F, 0xBF, 0x62, 0xA0, 0x8F, 0xBF, 0x62, 0xA0, 0x8F, 0x3F,
|
||||
0x62, 0xA0, 0x8F, 0x3F, 0x62, 0xA0, 0x8F, 0xBF, 0x62, 0xA0, 0x8F, 0x3F, 0x62, 0xA0,
|
||||
0x8F, 0xBF, 0x62, 0xA0, 0x8F, 0xBF, 0x62, 0xA0, 0x8F, 0xBF, 0x62, 0xA0, 0x8F, 0x3F,
|
||||
0x62, 0xA0, 0x8F, 0xBF, 0x62, 0xA0, 0x8F, 0xBF, 0x62, 0xA0, 0x8F, 0xBF,
|
||||
});
|
||||
|
||||
ASSERT_EQ(result.size(), expected.size());
|
||||
|
||||
for (int i = 0; i < result.size(); i++) {
|
||||
ASSERT_EQ(result[i], expected[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(PLYExportTest, WriteFacesAscii)
|
||||
{
|
||||
std::string filePath = get_temp_ply_filename(temp_file_path);
|
||||
PLYExportParams _params;
|
||||
_params.ascii_format = true;
|
||||
_params.export_normals = false;
|
||||
_params.vertex_colors = ePLYVertexColorMode::None;
|
||||
STRNCPY(_params.filepath, filePath.c_str());
|
||||
|
||||
std::unique_ptr<PlyData> plyData = load_cube(_params);
|
||||
|
||||
std::unique_ptr<FileBuffer> buffer = std::make_unique<FileBufferAscii>(_params.filepath);
|
||||
|
||||
write_faces(*buffer, *plyData);
|
||||
|
||||
buffer->close_file();
|
||||
|
||||
std::string result = read_temp_file_in_string(filePath);
|
||||
|
||||
std::string expected =
|
||||
"4 0 2 6 4\n"
|
||||
"4 3 7 6 2\n"
|
||||
"4 7 5 4 6\n"
|
||||
"4 5 7 3 1\n"
|
||||
"4 1 3 2 0\n"
|
||||
"4 5 1 0 4\n";
|
||||
|
||||
ASSERT_STREQ(result.c_str(), expected.c_str());
|
||||
}
|
||||
|
||||
TEST_F(PLYExportTest, WriteFacesBinary)
|
||||
{
|
||||
std::string filePath = get_temp_ply_filename(temp_file_path);
|
||||
PLYExportParams _params;
|
||||
_params.ascii_format = false;
|
||||
_params.export_normals = false;
|
||||
_params.vertex_colors = ePLYVertexColorMode::None;
|
||||
STRNCPY(_params.filepath, filePath.c_str());
|
||||
|
||||
std::unique_ptr<PlyData> plyData = load_cube(_params);
|
||||
|
||||
std::unique_ptr<FileBuffer> buffer = std::make_unique<FileBufferBinary>(_params.filepath);
|
||||
|
||||
write_faces(*buffer, *plyData);
|
||||
|
||||
buffer->close_file();
|
||||
|
||||
std::vector<char> result = read_temp_file_in_vectorchar(filePath);
|
||||
|
||||
std::vector<char> expected({
|
||||
0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00,
|
||||
0x00, 0x00, 0x04, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
|
||||
0x02, 0x00, 0x00, 0x00, 0x04, 0x07, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x04, 0x00,
|
||||
0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
|
||||
0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00,
|
||||
0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x05, 0x00, 0x00, 0x00,
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
|
||||
});
|
||||
|
||||
ASSERT_EQ(result.size(), expected.size());
|
||||
|
||||
for (int i = 0; i < result.size(); i++) {
|
||||
ASSERT_EQ(result[i], expected[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(PLYExportTest, WriteVertexNormalsAscii)
|
||||
{
|
||||
std::string filePath = get_temp_ply_filename(temp_file_path);
|
||||
PLYExportParams _params;
|
||||
_params.ascii_format = true;
|
||||
_params.export_normals = true;
|
||||
_params.vertex_colors = ePLYVertexColorMode::None;
|
||||
STRNCPY(_params.filepath, filePath.c_str());
|
||||
|
||||
std::unique_ptr<PlyData> plyData = load_cube(_params);
|
||||
|
||||
std::unique_ptr<FileBuffer> buffer = std::make_unique<FileBufferAscii>(_params.filepath);
|
||||
|
||||
write_vertices(*buffer, *plyData);
|
||||
|
||||
buffer->close_file();
|
||||
|
||||
std::string result = read_temp_file_in_string(filePath);
|
||||
|
||||
std::string expected =
|
||||
"1.122082 1.122082 1.122082 -0.5773503 -0.5773503 -0.5773503\n"
|
||||
"1.122082 1.122082 -1.122082 -0.5773503 -0.5773503 0.5773503\n"
|
||||
"1.122082 -1.122082 1.122082 -0.5773503 0.5773503 -0.5773503\n"
|
||||
"1.122082 -1.122082 -1.122082 -0.5773503 0.5773503 0.5773503\n"
|
||||
"-1.122082 1.122082 1.122082 0.5773503 -0.5773503 -0.5773503\n"
|
||||
"-1.122082 1.122082 -1.122082 0.5773503 -0.5773503 0.5773503\n"
|
||||
"-1.122082 -1.122082 1.122082 0.5773503 0.5773503 -0.5773503\n"
|
||||
"-1.122082 -1.122082 -1.122082 0.5773503 0.5773503 0.5773503\n";
|
||||
|
||||
ASSERT_STREQ(result.c_str(), expected.c_str());
|
||||
}
|
||||
|
||||
TEST_F(PLYExportTest, WriteVertexNormalsBinary)
|
||||
{
|
||||
std::string filePath = get_temp_ply_filename(temp_file_path);
|
||||
PLYExportParams _params;
|
||||
_params.ascii_format = false;
|
||||
_params.export_normals = true;
|
||||
_params.vertex_colors = ePLYVertexColorMode::None;
|
||||
STRNCPY(_params.filepath, filePath.c_str());
|
||||
|
||||
std::unique_ptr<PlyData> plyData = load_cube(_params);
|
||||
|
||||
std::unique_ptr<FileBuffer> buffer = std::make_unique<FileBufferBinary>(_params.filepath);
|
||||
|
||||
write_vertices(*buffer, *plyData);
|
||||
|
||||
buffer->close_file();
|
||||
|
||||
std::vector<char> result = read_temp_file_in_vectorchar(filePath);
|
||||
|
||||
std::vector<char> expected({
|
||||
0x62, 0xA0, 0x8F, 0x3F, 0x62, 0xA0, 0x8F, 0x3F, 0x62, 0xA0, 0x8F, 0x3F, 0x3B, 0xCD, 0x13,
|
||||
0xBF, 0x3B, 0xCD, 0x13, 0xBF, 0x3B, 0xCD, 0x13, 0xBF, 0x62, 0xA0, 0x8F, 0x3F, 0x62, 0xA0,
|
||||
0x8F, 0x3F, 0x62, 0xA0, 0x8F, 0xBF, 0x3B, 0xCD, 0x13, 0xBF, 0x3B, 0xCD, 0x13, 0xBF, 0x3B,
|
||||
0xCD, 0x13, 0x3F, 0x62, 0xA0, 0x8F, 0x3F, 0x62, 0xA0, 0x8F, 0xBF, 0x62, 0xA0, 0x8F, 0x3F,
|
||||
0x3B, 0xCD, 0x13, 0xBF, 0x3B, 0xCD, 0x13, 0x3F, 0x3B, 0xCD, 0x13, 0xBF, 0x62, 0xA0, 0x8F,
|
||||
0x3F, 0x62, 0xA0, 0x8F, 0xBF, 0x62, 0xA0, 0x8F, 0xBF, 0x3B, 0xCD, 0x13, 0xBF, 0x3B, 0xCD,
|
||||
0x13, 0x3F, 0x3B, 0xCD, 0x13, 0x3F, 0x62, 0xA0, 0x8F, 0xBF, 0x62, 0xA0, 0x8F, 0x3F, 0x62,
|
||||
0xA0, 0x8F, 0x3F, 0x3B, 0xCD, 0x13, 0x3F, 0x3B, 0xCD, 0x13, 0xBF, 0x3B, 0xCD, 0x13, 0xBF,
|
||||
0x62, 0xA0, 0x8F, 0xBF, 0x62, 0xA0, 0x8F, 0x3F, 0x62, 0xA0, 0x8F, 0xBF, 0x3B, 0xCD, 0x13,
|
||||
0x3F, 0x3B, 0xCD, 0x13, 0xBF, 0x3B, 0xCD, 0x13, 0x3F, 0x62, 0xA0, 0x8F, 0xBF, 0x62, 0xA0,
|
||||
0x8F, 0xBF, 0x62, 0xA0, 0x8F, 0x3F, 0x3B, 0xCD, 0x13, 0x3F, 0x3B, 0xCD, 0x13, 0x3F, 0x3B,
|
||||
0xCD, 0x13, 0xBF, 0x62, 0xA0, 0x8F, 0xBF, 0x62, 0xA0, 0x8F, 0xBF, 0x62, 0xA0, 0x8F, 0xBF,
|
||||
0x3B, 0xCD, 0x13, 0x3F, 0x3B, 0xCD, 0x13, 0x3F, 0x3B, 0xCD, 0x13, 0x3F,
|
||||
});
|
||||
|
||||
ASSERT_EQ(result.size(), expected.size());
|
||||
|
||||
for (int i = 0; i < result.size(); i++) {
|
||||
ASSERT_EQ(result[i], expected[i]);
|
||||
}
|
||||
}
|
||||
|
||||
class PLYExportPLYDataTest : public PLYExportTest {
|
||||
public:
|
||||
PlyData load_ply_data_from_blendfile(const std::string &blendfile, PLYExportParams ¶ms)
|
||||
{
|
||||
PlyData data;
|
||||
if (!load_file_and_depsgraph(blendfile)) {
|
||||
return data;
|
||||
}
|
||||
|
||||
load_plydata(data, depsgraph, params);
|
||||
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(PLYExportPLYDataTest, CubeLoadPLYData)
|
||||
{
|
||||
PLYExportParams params;
|
||||
params.export_uv = false;
|
||||
PlyData plyData = load_ply_data_from_blendfile("io_tests/blend_geometry/cube_all_data.blend",
|
||||
params);
|
||||
EXPECT_EQ(plyData.vertices.size(), 8);
|
||||
EXPECT_EQ(plyData.uv_coordinates.size(), 0);
|
||||
}
|
||||
TEST_F(PLYExportPLYDataTest, CubeLoadPLYDataUV)
|
||||
{
|
||||
PLYExportParams params;
|
||||
params.export_uv = true;
|
||||
PlyData plyData = load_ply_data_from_blendfile("io_tests/blend_geometry/cube_all_data.blend",
|
||||
params);
|
||||
EXPECT_EQ(plyData.vertices.size(), 8);
|
||||
EXPECT_EQ(plyData.uv_coordinates.size(), 8);
|
||||
}
|
||||
TEST_F(PLYExportPLYDataTest, CubeLooseEdgesLoadPLYData)
|
||||
{
|
||||
PLYExportParams params;
|
||||
params.export_uv = false;
|
||||
params.forward_axis = IO_AXIS_Y;
|
||||
params.up_axis = IO_AXIS_Z;
|
||||
params.global_scale = 1.0f;
|
||||
PlyData plyData = load_ply_data_from_blendfile(
|
||||
"io_tests/blend_geometry/cube_loose_edges_verts.blend", params);
|
||||
float3 exp_vertices[] = {
|
||||
{1, 1, 1},
|
||||
{1, 1, -1},
|
||||
{1, -1, 1},
|
||||
{1, -1, -1},
|
||||
{-1, 1, 1},
|
||||
{-1, 1, -1},
|
||||
{-1, -1, 1},
|
||||
{-1, -1, -1},
|
||||
};
|
||||
std::pair<int, int> exp_edges[] = {{7, 6}, {6, 4}};
|
||||
uint32_t exp_face_sizes[] = {4, 4};
|
||||
uint32_t exp_faces[] = {5, 1, 3, 7, 5, 4, 0, 1};
|
||||
EXPECT_EQ(plyData.vertices.size(), ARRAY_SIZE(exp_vertices));
|
||||
EXPECT_EQ(plyData.uv_coordinates.size(), 0);
|
||||
EXPECT_EQ(plyData.edges.size(), ARRAY_SIZE(exp_edges));
|
||||
EXPECT_EQ(plyData.face_sizes.size(), ARRAY_SIZE(exp_face_sizes));
|
||||
EXPECT_EQ(plyData.face_vertices.size(), ARRAY_SIZE(exp_faces));
|
||||
EXPECT_EQ_ARRAY(exp_vertices, plyData.vertices.data(), ARRAY_SIZE(exp_vertices));
|
||||
EXPECT_EQ_ARRAY(exp_edges, plyData.edges.data(), ARRAY_SIZE(exp_edges));
|
||||
EXPECT_EQ_ARRAY(exp_face_sizes, plyData.face_sizes.data(), ARRAY_SIZE(exp_face_sizes));
|
||||
EXPECT_EQ_ARRAY(exp_faces, plyData.face_vertices.data(), ARRAY_SIZE(exp_faces));
|
||||
}
|
||||
TEST_F(PLYExportPLYDataTest, CubeLooseEdgesLoadPLYDataUV)
|
||||
{
|
||||
PLYExportParams params;
|
||||
params.forward_axis = IO_AXIS_Y;
|
||||
params.up_axis = IO_AXIS_Z;
|
||||
params.global_scale = 1.0f;
|
||||
params.export_uv = true;
|
||||
PlyData plyData = load_ply_data_from_blendfile(
|
||||
"io_tests/blend_geometry/cube_loose_edges_verts.blend", params);
|
||||
float3 exp_vertices[] = {
|
||||
{-1, 1, -1},
|
||||
{1, 1, -1},
|
||||
{1, -1, -1},
|
||||
{-1, -1, -1},
|
||||
{-1, 1, -1},
|
||||
{-1, 1, 1},
|
||||
{1, 1, 1},
|
||||
{1, -1, 1},
|
||||
{-1, -1, 1},
|
||||
};
|
||||
float2 exp_uv[] = {
|
||||
{0.125f, 0.5f},
|
||||
{0.375f, 0.5f},
|
||||
{0.375f, 0.75f},
|
||||
{0.125f, 0.75f},
|
||||
{0.375f, 0.25f},
|
||||
{0.625f, 0.25f},
|
||||
{0.625f, 0.5f},
|
||||
{0, 0},
|
||||
{0, 0},
|
||||
};
|
||||
std::pair<int, int> exp_edges[] = {{3, 8}, {8, 5}};
|
||||
uint32_t exp_face_sizes[] = {4, 4};
|
||||
uint32_t exp_faces[] = {0, 1, 2, 3, 4, 5, 6, 1};
|
||||
EXPECT_EQ(plyData.vertices.size(), 9);
|
||||
EXPECT_EQ(plyData.uv_coordinates.size(), 9);
|
||||
EXPECT_EQ(plyData.edges.size(), ARRAY_SIZE(exp_edges));
|
||||
EXPECT_EQ(plyData.face_sizes.size(), ARRAY_SIZE(exp_face_sizes));
|
||||
EXPECT_EQ(plyData.face_vertices.size(), ARRAY_SIZE(exp_faces));
|
||||
EXPECT_EQ_ARRAY(exp_vertices, plyData.vertices.data(), ARRAY_SIZE(exp_vertices));
|
||||
EXPECT_EQ_ARRAY(exp_uv, plyData.uv_coordinates.data(), ARRAY_SIZE(exp_uv));
|
||||
EXPECT_EQ_ARRAY(exp_edges, plyData.edges.data(), ARRAY_SIZE(exp_edges));
|
||||
EXPECT_EQ_ARRAY(exp_face_sizes, plyData.face_sizes.data(), ARRAY_SIZE(exp_face_sizes));
|
||||
EXPECT_EQ_ARRAY(exp_faces, plyData.face_vertices.data(), ARRAY_SIZE(exp_faces));
|
||||
}
|
||||
|
||||
TEST_F(PLYExportPLYDataTest, CubesVertexAttrs)
|
||||
{
|
||||
PLYExportParams params;
|
||||
params.export_uv = true;
|
||||
params.export_attributes = true;
|
||||
PlyData plyData = load_ply_data_from_blendfile(
|
||||
"io_tests/blend_geometry/cubes_vertex_attrs.blend", params);
|
||||
EXPECT_EQ(plyData.vertices.size(), 28);
|
||||
EXPECT_EQ(plyData.vertex_custom_attr.size(), 11); /* Float 1 + Color 4 + ByteColor 4 + Int2D 2 */
|
||||
EXPECT_EQ(plyData.vertex_custom_attr[0].data.size(), 28);
|
||||
}
|
||||
|
||||
TEST_F(PLYExportPLYDataTest, SuzanneLoadPLYDataUV)
|
||||
{
|
||||
PLYExportParams params;
|
||||
params.export_uv = true;
|
||||
PlyData plyData = load_ply_data_from_blendfile("io_tests/blend_geometry/suzanne_all_data.blend",
|
||||
params);
|
||||
EXPECT_EQ(plyData.uv_coordinates.size(), 542);
|
||||
}
|
||||
|
||||
} // namespace blender::io::ply
|
||||
@@ -0,0 +1,77 @@
|
||||
/* SPDX-FileCopyrightText: 2023-2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "testing/testing.h"
|
||||
|
||||
#include "BLI_path_utils.hh"
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
#include "ply_import.hh"
|
||||
#include "ply_import_buffer.hh"
|
||||
#include "ply_import_data.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
static CLG_LogRef LOG = {"io.ply"};
|
||||
|
||||
namespace io::ply {
|
||||
|
||||
/* Extensive tests for PLY importing are in `io_ply_import_test.py`.
|
||||
* The tests here are only for testing PLY reader buffer refill behavior,
|
||||
* by using a very small buffer size on purpose. */
|
||||
|
||||
TEST(ply_import, BufferRefillTest)
|
||||
{
|
||||
std::string ply_path_a = tests::flags_test_asset_dir() +
|
||||
SEP_STR "io_tests" SEP_STR "ply" SEP_STR + "ASCII_wireframe_cube.ply";
|
||||
std::string ply_path_b = tests::flags_test_asset_dir() +
|
||||
SEP_STR "io_tests" SEP_STR "ply" SEP_STR + "wireframe_cube.ply";
|
||||
|
||||
/* Use a small read buffer size to test buffer refilling behavior. */
|
||||
constexpr size_t buffer_size = 50;
|
||||
PlyReadBuffer infile_a(ply_path_a.c_str(), buffer_size);
|
||||
PlyReadBuffer infile_b(ply_path_b.c_str(), buffer_size);
|
||||
PlyHeader header_a, header_b;
|
||||
const char *header_err_a = read_header(infile_a, header_a);
|
||||
const char *header_err_b = read_header(infile_b, header_b);
|
||||
if (header_err_a != nullptr || header_err_b != nullptr) {
|
||||
CLOG_ERROR(&LOG, "Failed to read PLY header");
|
||||
ADD_FAILURE();
|
||||
return;
|
||||
}
|
||||
std::unique_ptr<PlyData> data_a = import_ply_data(infile_a, header_a);
|
||||
std::unique_ptr<PlyData> data_b = import_ply_data(infile_b, header_b);
|
||||
if (!data_a->error.empty() || !data_b->error.empty()) {
|
||||
CLOG_ERROR(&LOG, "Failed to read PLY data");
|
||||
ADD_FAILURE();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check whether the edges list matches expectations. */
|
||||
std::pair<int, int> exp_edges[] = {{2, 0},
|
||||
{0, 1},
|
||||
{1, 3},
|
||||
{3, 2},
|
||||
{6, 2},
|
||||
{3, 7},
|
||||
{7, 6},
|
||||
{4, 6},
|
||||
{7, 5},
|
||||
{5, 4},
|
||||
{0, 4},
|
||||
{5, 1}};
|
||||
EXPECT_EQ_SPAN<std::pair<int, int>>(Span(exp_edges, 12), data_a->edges);
|
||||
EXPECT_EQ_SPAN<std::pair<int, int>>(Span(exp_edges, 12), data_b->edges);
|
||||
}
|
||||
|
||||
//@TODO: now we put vertex color attribute first, maybe put position first?
|
||||
//@TODO: test with vertex element having list properties
|
||||
//@TODO: test with edges starting with non-vertex index properties
|
||||
//@TODO: test various malformed headers
|
||||
//@TODO: UVs with: s,t; u,v; texture_u,texture_v; texture_s,texture_t (from miniply)
|
||||
//@TODO: colors with: r,g,b in addition to red,green,blue (from miniply)
|
||||
|
||||
} // namespace io::ply
|
||||
} // namespace blender
|
||||
Reference in New Issue
Block a user