IO: New C++ PLY importer/exporter
New (experimental) Stanford PLY importer and exporter written in C++. Handles: vertices, faces, edges, vertex colors, normals, UVs. Both binary and ASCII formats are supported. Usually 10-20x faster than the existing Python based PLY importer/exporter. Additional notes compared to the previous Python addon: - Importing point clouds with vertex colors now works - Importing PLY files with non standard line endings - Exporting multiple objects (previous exporter didn't take the vertex indices into account) - The importer has the option to merge vertices - The exporter supports exporting loose edges and vertices along with UV map data This is squashed commit of PR #104404 Reviewed By: Hans Goudey, Aras Pranckevicius Co-authored-by: Arjan van Diest Co-authored-by: Lilith Houtjes Co-authored-by: Bas Hendriks Co-authored-by: Thomas Feijen Co-authored-by: Yoran Huzen
This commit is contained in:
parent
b4d36b3efe
commit
43e9c90061
|
@ -357,6 +357,7 @@ option(WITH_MATERIALX "Enable MaterialX Support" OFF)
|
|||
# Disable opencollada when we don't have precompiled libs
|
||||
option(WITH_OPENCOLLADA "Enable OpenCollada Support (http://www.opencollada.org)" ON)
|
||||
option(WITH_IO_WAVEFRONT_OBJ "Enable Wavefront-OBJ 3D file format support (*.obj)" ON)
|
||||
option(WITH_IO_PLY "Enable PLY 3D file format support (*.ply)" ON)
|
||||
option(WITH_IO_STL "Enable STL 3D file format support (*.stl)" ON)
|
||||
option(WITH_IO_GPENCIL "Enable grease-pencil file format IO (*.svg, *.pdf)" ON)
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ set(WITH_IMAGE_WEBP OFF CACHE BOOL "" FORCE)
|
|||
set(WITH_INPUT_IME OFF CACHE BOOL "" FORCE)
|
||||
set(WITH_INPUT_NDOF OFF CACHE BOOL "" FORCE)
|
||||
set(WITH_INTERNATIONAL OFF CACHE BOOL "" FORCE)
|
||||
set(WITH_IO_PLY OFF CACHE BOOL "" FORCE)
|
||||
set(WITH_IO_STL OFF CACHE BOOL "" FORCE)
|
||||
set(WITH_IO_WAVEFRONT_OBJ OFF CACHE BOOL "" FORCE)
|
||||
set(WITH_IO_GPENCIL OFF CACHE BOOL "" FORCE)
|
||||
|
|
|
@ -475,6 +475,8 @@ class TOPBAR_MT_file_import(Menu):
|
|||
|
||||
if bpy.app.build_options.io_wavefront_obj:
|
||||
self.layout.operator("wm.obj_import", text="Wavefront (.obj)")
|
||||
if bpy.app.build_options.io_ply:
|
||||
self.layout.operator("wm.ply_import", text="PLY (.ply) (experimental)")
|
||||
if bpy.app.build_options.io_stl:
|
||||
self.layout.operator("wm.stl_import", text="STL (.stl) (experimental)")
|
||||
|
||||
|
@ -503,6 +505,8 @@ class TOPBAR_MT_file_export(Menu):
|
|||
|
||||
if bpy.app.build_options.io_wavefront_obj:
|
||||
self.layout.operator("wm.obj_export", text="Wavefront (.obj)")
|
||||
if bpy.app.build_options.io_ply:
|
||||
self.layout.operator("wm.ply_export", text="PLY (.ply) (experimental)")
|
||||
|
||||
|
||||
class TOPBAR_MT_file_external_data(Menu):
|
||||
|
|
|
@ -11,6 +11,7 @@ set(INC
|
|||
../../io/collada
|
||||
../../io/common
|
||||
../../io/gpencil
|
||||
../../io/ply
|
||||
../../io/stl
|
||||
../../io/usd
|
||||
../../io/wavefront_obj
|
||||
|
@ -33,6 +34,7 @@ set(SRC
|
|||
io_gpencil_utils.c
|
||||
io_obj.c
|
||||
io_ops.c
|
||||
io_ply_ops.c
|
||||
io_stl_ops.c
|
||||
io_usd.c
|
||||
|
||||
|
@ -42,6 +44,7 @@ set(SRC
|
|||
io_gpencil.h
|
||||
io_obj.h
|
||||
io_ops.h
|
||||
io_ply_ops.h
|
||||
io_stl_ops.h
|
||||
io_usd.h
|
||||
)
|
||||
|
@ -65,6 +68,13 @@ if(WITH_IO_WAVEFRONT_OBJ)
|
|||
add_definitions(-DWITH_IO_WAVEFRONT_OBJ)
|
||||
endif()
|
||||
|
||||
if(WITH_IO_PLY)
|
||||
list(APPEND LIB
|
||||
bf_ply
|
||||
)
|
||||
add_definitions(-DWITH_IO_PLY)
|
||||
endif()
|
||||
|
||||
if(WITH_IO_STL)
|
||||
list(APPEND LIB
|
||||
bf_stl
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include "io_cache.h"
|
||||
#include "io_gpencil.h"
|
||||
#include "io_obj.h"
|
||||
#include "io_ply_ops.h"
|
||||
#include "io_stl_ops.h"
|
||||
|
||||
void ED_operatortypes_io(void)
|
||||
|
@ -64,6 +65,11 @@ void ED_operatortypes_io(void)
|
|||
WM_operatortype_append(WM_OT_obj_import);
|
||||
#endif
|
||||
|
||||
#ifdef WITH_IO_PLY
|
||||
WM_operatortype_append(WM_OT_ply_export);
|
||||
WM_operatortype_append(WM_OT_ply_import);
|
||||
#endif
|
||||
|
||||
#ifdef WITH_IO_STL
|
||||
WM_operatortype_append(WM_OT_stl_import);
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,328 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup editor/io
|
||||
*/
|
||||
|
||||
#ifdef WITH_IO_PLY
|
||||
|
||||
# include "BKE_context.h"
|
||||
# include "BKE_main.h"
|
||||
# include "BKE_report.h"
|
||||
|
||||
# include "WM_api.h"
|
||||
# include "WM_types.h"
|
||||
|
||||
# include "DNA_space_types.h"
|
||||
|
||||
# include "ED_fileselect.h"
|
||||
# include "ED_outliner.h"
|
||||
|
||||
# include "RNA_access.h"
|
||||
# include "RNA_define.h"
|
||||
|
||||
# include "BLT_translation.h"
|
||||
|
||||
# include "MEM_guardedalloc.h"
|
||||
|
||||
# include "UI_interface.h"
|
||||
# include "UI_resources.h"
|
||||
|
||||
# include "DEG_depsgraph.h"
|
||||
|
||||
# include "IO_orientation.h"
|
||||
# include "IO_path_util_types.h"
|
||||
|
||||
# include "IO_ply.h"
|
||||
# include "io_ply_ops.h"
|
||||
|
||||
static int wm_ply_export_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
|
||||
{
|
||||
ED_fileselect_ensure_default_filepath(C, op, ".ply");
|
||||
|
||||
WM_event_add_fileselect(C, op);
|
||||
return OPERATOR_RUNNING_MODAL;
|
||||
}
|
||||
|
||||
static int wm_ply_export_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
if (!RNA_struct_property_is_set_ex(op->ptr, "filepath", false)) {
|
||||
BKE_report(op->reports, RPT_ERROR, "No filename given");
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
struct PLYExportParams export_params;
|
||||
export_params.file_base_for_tests[0] = '\0';
|
||||
RNA_string_get(op->ptr, "filepath", export_params.filepath);
|
||||
export_params.blen_filepath = CTX_data_main(C)->filepath;
|
||||
|
||||
export_params.forward_axis = RNA_enum_get(op->ptr, "forward_axis");
|
||||
export_params.up_axis = RNA_enum_get(op->ptr, "up_axis");
|
||||
export_params.global_scale = RNA_float_get(op->ptr, "global_scale");
|
||||
export_params.apply_modifiers = RNA_boolean_get(op->ptr, "apply_modifiers");
|
||||
|
||||
export_params.export_selected_objects = RNA_boolean_get(op->ptr, "export_selected_objects");
|
||||
export_params.export_uv = RNA_boolean_get(op->ptr, "export_uv");
|
||||
export_params.export_normals = RNA_boolean_get(op->ptr, "export_normals");
|
||||
export_params.export_colors = RNA_boolean_get(op->ptr, "export_colors");
|
||||
export_params.export_triangulated_mesh = RNA_boolean_get(op->ptr, "export_triangulated_mesh");
|
||||
export_params.ascii_format = RNA_boolean_get(op->ptr, "ascii_format");
|
||||
|
||||
PLY_export(C, &export_params);
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
static void ui_ply_export_settings(uiLayout *layout, PointerRNA *imfptr)
|
||||
{
|
||||
uiLayoutSetPropSep(layout, true);
|
||||
uiLayoutSetPropDecorate(layout, false);
|
||||
|
||||
uiLayout *box, *col, *sub;
|
||||
|
||||
/* Object Transform options. */
|
||||
box = uiLayoutBox(layout);
|
||||
col = uiLayoutColumn(box, false);
|
||||
sub = uiLayoutColumnWithHeading(col, false, IFACE_("Format"));
|
||||
uiItemR(sub, imfptr, "ascii_format", 0, IFACE_("ASCII"), ICON_NONE);
|
||||
sub = uiLayoutColumnWithHeading(col, false, IFACE_("Limit to"));
|
||||
uiItemR(sub, imfptr, "export_selected_objects", 0, IFACE_("Selected Only"), ICON_NONE);
|
||||
uiItemR(sub, imfptr, "global_scale", 0, NULL, ICON_NONE);
|
||||
|
||||
uiItemR(sub, imfptr, "forward_axis", 0, IFACE_("Forward Axis"), ICON_NONE);
|
||||
uiItemR(sub, imfptr, "up_axis", 0, IFACE_("Up Axis"), ICON_NONE);
|
||||
|
||||
col = uiLayoutColumn(box, false);
|
||||
sub = uiLayoutColumn(col, false);
|
||||
sub = uiLayoutColumnWithHeading(col, false, IFACE_("Objects"));
|
||||
uiItemR(sub, imfptr, "apply_modifiers", 0, IFACE_("Apply Modifiers"), ICON_NONE);
|
||||
|
||||
/* Geometry options. */
|
||||
box = uiLayoutBox(layout);
|
||||
col = uiLayoutColumn(box, false);
|
||||
sub = uiLayoutColumnWithHeading(col, false, IFACE_("Geometry"));
|
||||
uiItemR(sub, imfptr, "export_uv", 0, IFACE_("UV Coordinates"), ICON_NONE);
|
||||
uiItemR(sub, imfptr, "export_normals", 0, IFACE_("Vertex Normals"), ICON_NONE);
|
||||
uiItemR(sub, imfptr, "export_colors", 0, IFACE_("Vertex Colors"), ICON_NONE);
|
||||
uiItemR(sub, imfptr, "export_triangulated_mesh", 0, IFACE_("Triangulated Mesh"), ICON_NONE);
|
||||
}
|
||||
|
||||
static void wm_ply_export_draw(bContext *UNUSED(C), wmOperator *op)
|
||||
{
|
||||
PointerRNA ptr;
|
||||
RNA_pointer_create(NULL, op->type->srna, op->properties, &ptr);
|
||||
ui_ply_export_settings(op->layout, &ptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if any property in the UI is changed.
|
||||
*/
|
||||
static bool wm_ply_export_check(bContext *C, wmOperator *op)
|
||||
{
|
||||
char filepath[FILE_MAX];
|
||||
bool changed = false;
|
||||
RNA_string_get(op->ptr, "filepath", filepath);
|
||||
|
||||
if (!BLI_path_extension_check(filepath, ".ply")) {
|
||||
BLI_path_extension_ensure(filepath, FILE_MAX, ".ply");
|
||||
RNA_string_set(op->ptr, "filepath", filepath);
|
||||
changed = true;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
/* Both forward and up axes cannot be along the same direction. */
|
||||
static void forward_axis_update(struct Main *UNUSED(main),
|
||||
struct Scene *UNUSED(scene),
|
||||
struct PointerRNA *ptr)
|
||||
{
|
||||
int forward = RNA_enum_get(ptr, "forward_axis");
|
||||
int up = RNA_enum_get(ptr, "up_axis");
|
||||
if ((forward % 3) == (up % 3)) {
|
||||
RNA_enum_set(ptr, "up_axis", (up + 1) % 6);
|
||||
}
|
||||
}
|
||||
|
||||
static void up_axis_update(struct Main *UNUSED(main),
|
||||
struct Scene *UNUSED(scene),
|
||||
struct PointerRNA *ptr)
|
||||
{
|
||||
int forward = RNA_enum_get(ptr, "forward_axis");
|
||||
int up = RNA_enum_get(ptr, "up_axis");
|
||||
if ((forward % 3) == (up % 3)) {
|
||||
RNA_enum_set(ptr, "forward_axis", (forward + 1) % 6);
|
||||
}
|
||||
}
|
||||
|
||||
void WM_OT_ply_export(struct wmOperatorType *ot)
|
||||
{
|
||||
PropertyRNA *prop;
|
||||
|
||||
ot->name = "Export PLY";
|
||||
ot->description = "Save the scene to a PLY file";
|
||||
ot->idname = "WM_OT_ply_export";
|
||||
|
||||
ot->invoke = wm_ply_export_invoke;
|
||||
ot->exec = wm_ply_export_exec;
|
||||
ot->poll = WM_operator_winactive;
|
||||
ot->ui = wm_ply_export_draw;
|
||||
ot->check = wm_ply_export_check;
|
||||
|
||||
ot->flag = OPTYPE_PRESET;
|
||||
|
||||
WM_operator_properties_filesel(ot,
|
||||
FILE_TYPE_FOLDER,
|
||||
FILE_BLENDER,
|
||||
FILE_SAVE,
|
||||
WM_FILESEL_FILEPATH | WM_FILESEL_SHOW_PROPS,
|
||||
FILE_DEFAULTDISPLAY,
|
||||
FILE_SORT_DEFAULT);
|
||||
|
||||
/* Object transform options. */
|
||||
prop = RNA_def_enum(ot->srna, "forward_axis", io_transform_axis, IO_AXIS_Y, "Forward Axis", "");
|
||||
RNA_def_property_update_runtime(prop, (void *)forward_axis_update);
|
||||
prop = RNA_def_enum(ot->srna, "up_axis", io_transform_axis, IO_AXIS_Z, "Up Axis", "");
|
||||
RNA_def_property_update_runtime(prop, (void *)up_axis_update);
|
||||
RNA_def_float(
|
||||
ot->srna,
|
||||
"global_scale",
|
||||
1.0f,
|
||||
0.0001f,
|
||||
10000.0f,
|
||||
"Scale",
|
||||
"Value by which to enlarge or shrink the objects with respect to the world's origin",
|
||||
0.0001f,
|
||||
10000.0f);
|
||||
/* File Writer options. */
|
||||
RNA_def_boolean(
|
||||
ot->srna, "apply_modifiers", true, "Apply Modifiers", "Apply modifiers to exported meshes");
|
||||
RNA_def_boolean(ot->srna,
|
||||
"export_selected_objects",
|
||||
false,
|
||||
"Export Selected Objects",
|
||||
"Export only selected objects instead of all supported objects");
|
||||
RNA_def_boolean(ot->srna, "export_uv", false, "Export UVs", "");
|
||||
RNA_def_boolean(
|
||||
ot->srna,
|
||||
"export_normals",
|
||||
false,
|
||||
"Export Vertex Normals",
|
||||
"Export specific vertex normals if available, export calculated normals otherwise");
|
||||
RNA_def_boolean(
|
||||
ot->srna, "export_colors", true, "Export Vertex Colors", "Export per-vertex colors");
|
||||
RNA_def_boolean(ot->srna,
|
||||
"export_triangulated_mesh",
|
||||
false,
|
||||
"Export Triangulated Mesh",
|
||||
"All ngons with four or more vertices will be triangulated. Meshes in "
|
||||
"the scene will not be affected. Behaves like Triangulate Modifier with "
|
||||
"ngon-method: \"Beauty\", quad-method: \"Shortest Diagonal\", min vertices: 4");
|
||||
RNA_def_boolean(ot->srna,
|
||||
"ascii_format",
|
||||
false,
|
||||
"ASCII Format",
|
||||
"Export file in ASCII format, export as binary otherwise");
|
||||
|
||||
/* Only show .ply files by default. */
|
||||
prop = RNA_def_string(ot->srna, "filter_glob", "*.ply", 0, "Extension Filter", "");
|
||||
RNA_def_property_flag(prop, PROP_HIDDEN);
|
||||
}
|
||||
|
||||
static int wm_ply_import_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
||||
{
|
||||
return WM_operator_filesel(C, op, event);
|
||||
}
|
||||
|
||||
static int wm_ply_import_execute(bContext *C, wmOperator *op)
|
||||
{
|
||||
struct PLYImportParams params;
|
||||
params.forward_axis = RNA_enum_get(op->ptr, "forward_axis");
|
||||
params.up_axis = RNA_enum_get(op->ptr, "up_axis");
|
||||
params.use_scene_unit = RNA_boolean_get(op->ptr, "use_scene_unit");
|
||||
params.global_scale = RNA_float_get(op->ptr, "global_scale");
|
||||
params.merge_verts = RNA_boolean_get(op->ptr, "merge_verts");
|
||||
|
||||
int files_len = RNA_collection_length(op->ptr, "files");
|
||||
|
||||
if (files_len) {
|
||||
PointerRNA fileptr;
|
||||
PropertyRNA *prop;
|
||||
char dir_only[FILE_MAX], file_only[FILE_MAX];
|
||||
|
||||
RNA_string_get(op->ptr, "directory", dir_only);
|
||||
prop = RNA_struct_find_property(op->ptr, "files");
|
||||
for (int i = 0; i < files_len; i++) {
|
||||
RNA_property_collection_lookup_int(op->ptr, prop, i, &fileptr);
|
||||
RNA_string_get(&fileptr, "name", file_only);
|
||||
BLI_path_join(params.filepath, sizeof(params.filepath), dir_only, file_only);
|
||||
PLY_import(C, ¶ms, op);
|
||||
}
|
||||
}
|
||||
else if (RNA_struct_property_is_set_ex(op->ptr, "filepath", false)) {
|
||||
RNA_string_get(op->ptr, "filepath", params.filepath);
|
||||
PLY_import(C, ¶ms, op);
|
||||
}
|
||||
else {
|
||||
BKE_report(op->reports, RPT_ERROR, "No filename given");
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene);
|
||||
WM_event_add_notifier(C, NC_SCENE | ND_OB_ACTIVE, scene);
|
||||
WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene);
|
||||
ED_outliner_select_sync_from_object_tag(C);
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
static bool wm_ply_import_check(bContext *UNUSED(C), wmOperator *op)
|
||||
{
|
||||
const int num_axes = 3;
|
||||
/* Both forward and up axes cannot be the same (or same except opposite sign). */
|
||||
if (RNA_enum_get(op->ptr, "forward_axis") % num_axes ==
|
||||
(RNA_enum_get(op->ptr, "up_axis") % num_axes)) {
|
||||
RNA_enum_set(op->ptr, "up_axis", RNA_enum_get(op->ptr, "up_axis") % num_axes + 1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void WM_OT_ply_import(struct wmOperatorType *ot)
|
||||
{
|
||||
PropertyRNA *prop;
|
||||
|
||||
ot->name = "Import PLY";
|
||||
ot->description = "Import an PLY file as an object";
|
||||
ot->idname = "WM_OT_ply_import";
|
||||
|
||||
ot->invoke = wm_ply_import_invoke;
|
||||
ot->exec = wm_ply_import_execute;
|
||||
ot->poll = WM_operator_winactive;
|
||||
ot->check = wm_ply_import_check;
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_PRESET;
|
||||
|
||||
WM_operator_properties_filesel(ot,
|
||||
FILE_TYPE_FOLDER,
|
||||
FILE_BLENDER,
|
||||
FILE_OPENFILE,
|
||||
WM_FILESEL_FILEPATH | WM_FILESEL_FILES | WM_FILESEL_DIRECTORY |
|
||||
WM_FILESEL_SHOW_PROPS,
|
||||
FILE_DEFAULTDISPLAY,
|
||||
FILE_SORT_DEFAULT);
|
||||
|
||||
RNA_def_float(ot->srna, "global_scale", 1.0f, 1e-6f, 1e6f, "Scale", "", 0.001f, 1000.0f);
|
||||
RNA_def_boolean(ot->srna,
|
||||
"use_scene_unit",
|
||||
false,
|
||||
"Scene Unit",
|
||||
"Apply current scene's unit (as defined by unit scale) to imported data");
|
||||
RNA_def_enum(ot->srna, "forward_axis", io_transform_axis, IO_AXIS_Y, "Forward Axis", "");
|
||||
RNA_def_enum(ot->srna, "up_axis", io_transform_axis, IO_AXIS_Z, "Up Axis", "");
|
||||
RNA_def_boolean(ot->srna, "merge_verts", false, "Merge Vertices", "Merges vertices by distance");
|
||||
|
||||
/* Only show .ply files by default. */
|
||||
prop = RNA_def_string(ot->srna, "filter_glob", "*.ply", 0, "Extension Filter", "");
|
||||
RNA_def_property_flag(prop, PROP_HIDDEN);
|
||||
}
|
||||
|
||||
#endif /* WITH_IO_PLY */
|
|
@ -0,0 +1,12 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup editor/io
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
struct wmOperatorType;
|
||||
|
||||
void WM_OT_ply_export(struct wmOperatorType *ot);
|
||||
void WM_OT_ply_import(struct wmOperatorType *ot);
|
|
@ -2678,7 +2678,7 @@ int ED_path_extension_type(const char *path)
|
|||
return FILE_TYPE_ARCHIVE;
|
||||
}
|
||||
if (BLI_path_extension_check_n(
|
||||
path, ".obj", ".mtl", ".3ds", ".fbx", ".glb", ".gltf", ".svg", ".stl", nullptr)) {
|
||||
path, ".obj", ".mtl", ".3ds", ".fbx", ".glb", ".gltf", ".svg", ".ply", ".stl", nullptr)) {
|
||||
return FILE_TYPE_OBJECT_IO;
|
||||
}
|
||||
if (BLI_path_extension_check_array(path, imb_ext_image)) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Copyright 2020 Blender Foundation. All rights reserved.
|
||||
|
||||
if(WITH_IO_WAVEFRONT_OBJ OR WITH_IO_STL OR WITH_IO_GPENCIL OR WITH_ALEMBIC OR WITH_USD)
|
||||
if(WITH_IO_WAVEFRONT_OBJ OR WITH_IO_PLY OR WITH_IO_STL OR WITH_IO_GPENCIL OR WITH_ALEMBIC OR WITH_USD)
|
||||
add_subdirectory(common)
|
||||
endif()
|
||||
|
||||
|
@ -9,6 +9,10 @@ if(WITH_IO_WAVEFRONT_OBJ)
|
|||
add_subdirectory(wavefront_obj)
|
||||
endif()
|
||||
|
||||
if(WITH_IO_PLY)
|
||||
add_subdirectory(ply)
|
||||
endif()
|
||||
|
||||
if(WITH_IO_STL)
|
||||
add_subdirectory(stl)
|
||||
endif()
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
set(INC
|
||||
.
|
||||
exporter
|
||||
importer
|
||||
intern
|
||||
../common
|
||||
../../blenkernel
|
||||
../../blenlib
|
||||
../../bmesh
|
||||
../../depsgraph
|
||||
../../makesdna
|
||||
../../makesrna
|
||||
../../windowmanager
|
||||
../../geometry
|
||||
../../../../extern/fmtlib/include
|
||||
../../../../intern/guardedalloc
|
||||
)
|
||||
|
||||
set(INC_SYS
|
||||
|
||||
)
|
||||
|
||||
set(SRC
|
||||
exporter/ply_export_data.cc
|
||||
exporter/ply_export_header.cc
|
||||
exporter/ply_export_load_plydata.cc
|
||||
exporter/ply_export.cc
|
||||
exporter/ply_file_buffer_ascii.cc
|
||||
exporter/ply_file_buffer_binary.cc
|
||||
exporter/ply_file_buffer.cc
|
||||
importer/ply_import_ascii.cc
|
||||
importer/ply_import_binary.cc
|
||||
importer/ply_import_mesh.cc
|
||||
importer/ply_import.cc
|
||||
IO_ply.cc
|
||||
|
||||
|
||||
|
||||
exporter/ply_export_data.hh
|
||||
exporter/ply_export_header.hh
|
||||
exporter/ply_export_load_plydata.hh
|
||||
exporter/ply_export.hh
|
||||
exporter/ply_file_buffer_ascii.hh
|
||||
exporter/ply_file_buffer_binary.hh
|
||||
exporter/ply_file_buffer.hh
|
||||
importer/ply_import_ascii.hh
|
||||
importer/ply_import_binary.hh
|
||||
importer/ply_import_mesh.hh
|
||||
importer/ply_import.hh
|
||||
IO_ply.h
|
||||
|
||||
intern/ply_data.hh
|
||||
intern/ply_functions.hh
|
||||
intern/ply_functions.cc
|
||||
)
|
||||
|
||||
set(LIB
|
||||
bf_blenkernel
|
||||
bf_io_common
|
||||
)
|
||||
|
||||
blender_add_lib(bf_ply "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
|
||||
|
||||
if (WITH_GTESTS)
|
||||
set(TEST_SRC
|
||||
tests/io_ply_importer_test.cc
|
||||
|
||||
tests/io_ply_exporter_test.cc
|
||||
|
||||
)
|
||||
set(TEST_INC
|
||||
../../blenloader
|
||||
../../../../tests/gtests
|
||||
)
|
||||
set(TEST_LIB
|
||||
bf_ply
|
||||
)
|
||||
include(GTestTesting)
|
||||
blender_add_test_lib(bf_io_ply_tests "${TEST_SRC}" "${INC};${TEST_INC}" "${INC_SYS}" "${LIB};${TEST_LIB}")
|
||||
endif()
|
|
@ -0,0 +1,24 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#include "BLI_timeit.hh"
|
||||
|
||||
#include "DNA_windowmanager_types.h"
|
||||
#include "IO_ply.h"
|
||||
#include "ply_export.hh"
|
||||
#include "ply_import.hh"
|
||||
|
||||
void PLY_export(bContext *C, const PLYExportParams *export_params)
|
||||
{
|
||||
SCOPED_TIMER("PLY Export");
|
||||
blender::io::ply::exporter_main(C, *export_params);
|
||||
}
|
||||
|
||||
void PLY_import(bContext *C, const PLYImportParams *import_params, wmOperator *op)
|
||||
{
|
||||
SCOPED_TIMER("PLY Import");
|
||||
blender::io::ply::importer_main(C, *import_params, op);
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BKE_context.h"
|
||||
|
||||
#include "BLI_path_util.h"
|
||||
#include "DNA_windowmanager_types.h"
|
||||
#include "IO_orientation.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
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;
|
||||
|
||||
/** File export format, ASCII if true, binary otherwise. */
|
||||
bool ascii_format;
|
||||
|
||||
/* Geometry Transform options. */
|
||||
eIOAxis forward_axis;
|
||||
eIOAxis up_axis;
|
||||
float global_scale;
|
||||
|
||||
/* File Write Options. */
|
||||
bool export_selected_objects;
|
||||
bool apply_modifiers;
|
||||
bool export_uv;
|
||||
bool export_normals;
|
||||
bool export_colors;
|
||||
bool export_triangulated_mesh;
|
||||
};
|
||||
|
||||
struct PLYImportParams {
|
||||
/** Full path to the source PLY file to import. */
|
||||
char filepath[FILE_MAX];
|
||||
eIOAxis forward_axis;
|
||||
eIOAxis up_axis;
|
||||
bool use_scene_unit;
|
||||
float global_scale;
|
||||
bool merge_verts;
|
||||
};
|
||||
|
||||
/**
|
||||
* C-interface for the importer and exporter.
|
||||
*/
|
||||
void PLY_export(bContext *C, const struct PLYExportParams *export_params);
|
||||
|
||||
void PLY_import(bContext *C, const struct PLYImportParams *import_params, wmOperator *op);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,60 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#include "BKE_layer.h"
|
||||
|
||||
#include "DNA_collection_types.h"
|
||||
#include "DNA_scene_types.h"
|
||||
|
||||
#include "BLI_memory_utils.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"
|
||||
|
||||
namespace blender::io::ply {
|
||||
|
||||
void exporter_main(bContext *C, const PLYExportParams &export_params)
|
||||
{
|
||||
Main *bmain = CTX_data_main(C);
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
ViewLayer *view_layer = CTX_data_view_layer(C);
|
||||
exporter_main(bmain, scene, view_layer, C, export_params);
|
||||
}
|
||||
|
||||
void exporter_main(Main *bmain,
|
||||
Scene *scene,
|
||||
ViewLayer *view_layer,
|
||||
bContext *C,
|
||||
const PLYExportParams &export_params)
|
||||
{
|
||||
std::unique_ptr<blender::io::ply::PlyData> plyData = std::make_unique<PlyData>();
|
||||
load_plydata(*plyData, CTX_data_ensure_evaluated_depsgraph(C), export_params);
|
||||
|
||||
std::unique_ptr<FileBuffer> buffer;
|
||||
|
||||
if (export_params.ascii_format) {
|
||||
buffer = std::make_unique<FileBufferAscii>(export_params.filepath);
|
||||
}
|
||||
else {
|
||||
buffer = std::make_unique<FileBufferBinary>(export_params.filepath);
|
||||
}
|
||||
|
||||
write_header(*buffer.get(), *plyData.get(), export_params);
|
||||
|
||||
write_vertices(*buffer.get(), *plyData.get());
|
||||
|
||||
write_faces(*buffer.get(), *plyData.get());
|
||||
|
||||
write_edges(*buffer.get(), *plyData.get());
|
||||
|
||||
buffer->close_file();
|
||||
}
|
||||
} // namespace blender::io::ply
|
|
@ -0,0 +1,25 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IO_ply.h"
|
||||
#include "ply_data.hh"
|
||||
#include "ply_file_buffer.hh"
|
||||
|
||||
namespace blender::io::ply {
|
||||
|
||||
/* Main export function used from within Blender. */
|
||||
void exporter_main(bContext *C, const PLYExportParams &export_params);
|
||||
|
||||
/* Used from tests, where full bContext does not exist. */
|
||||
void exporter_main(Main *bmain,
|
||||
Scene *scene,
|
||||
ViewLayer *view_layer,
|
||||
bContext *C,
|
||||
const PLYExportParams &export_params);
|
||||
|
||||
} // namespace blender::io::ply
|
|
@ -0,0 +1,51 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
#include "BLI_array.hh"
|
||||
|
||||
#include "ply_data.hh"
|
||||
#include "ply_file_buffer.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())
|
||||
buffer.write_vertex_color(uchar(ply_data.vertex_colors[i].x * 255),
|
||||
uchar(ply_data.vertex_colors[i].y * 255),
|
||||
uchar(ply_data.vertex_colors[i].z * 255),
|
||||
uchar(ply_data.vertex_colors[i].w * 255));
|
||||
|
||||
if (!ply_data.UV_coordinates.is_empty())
|
||||
buffer.write_UV(ply_data.UV_coordinates[i].x, ply_data.UV_coordinates[i].y);
|
||||
|
||||
buffer.write_vertex_end();
|
||||
}
|
||||
buffer.write_to_file();
|
||||
}
|
||||
|
||||
void write_faces(FileBuffer &buffer, const PlyData &ply_data)
|
||||
{
|
||||
for (const Array<uint32_t> &face : ply_data.faces) {
|
||||
buffer.write_face(char(face.size()), face);
|
||||
}
|
||||
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,20 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ply_data.hh"
|
||||
#include "ply_file_buffer.hh"
|
||||
|
||||
namespace blender::io::ply {
|
||||
|
||||
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,66 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#include "BKE_blender_version.h"
|
||||
#include "BKE_customdata.h"
|
||||
|
||||
#include "IO_ply.h"
|
||||
#include "ply_data.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");
|
||||
}
|
||||
|
||||
if (!ply_data.faces.is_empty()) {
|
||||
buffer.write_header_element("face", int32_t(ply_data.faces.size()));
|
||||
buffer.write_header_list_property("uchar", "uint", "vertex_indices");
|
||||
}
|
||||
|
||||
if (!ply_data.edges.is_empty()) {
|
||||
buffer.write_header_element("edge", int32_t(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,18 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ply_data.hh"
|
||||
#include "ply_file_buffer.hh"
|
||||
|
||||
namespace blender::io::ply {
|
||||
|
||||
void write_header(FileBuffer &buffer,
|
||||
const PlyData &ply_data,
|
||||
const PLYExportParams &export_params);
|
||||
|
||||
} // namespace blender::io::ply
|
|
@ -0,0 +1,273 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#include "BLI_array.hh"
|
||||
#include "BLI_math.h"
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_lib_id.h"
|
||||
#include "BKE_mesh.h"
|
||||
#include "BKE_mesh_mapping.h"
|
||||
#include "BKE_object.h"
|
||||
|
||||
#include "DEG_depsgraph.h"
|
||||
#include "DEG_depsgraph_build.h"
|
||||
#include "DEG_depsgraph_query.h"
|
||||
|
||||
#include "DNA_layer_types.h"
|
||||
|
||||
#include "IO_ply.h"
|
||||
|
||||
#include "bmesh.h"
|
||||
#include "bmesh_tools.h"
|
||||
|
||||
#include <tools/bmesh_triangulate.h>
|
||||
|
||||
#include "ply_data.hh"
|
||||
#include "ply_export_load_plydata.hh"
|
||||
|
||||
namespace blender::io::ply {
|
||||
|
||||
float world_and_axes_transform_[4][4];
|
||||
float world_and_axes_normal_transform_[3][3];
|
||||
bool mirrored_transform_;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void set_world_axes_transform(Object *object, const eIOAxis forward, const eIOAxis up)
|
||||
{
|
||||
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(world_and_axes_transform_, axes_transform, object->object_to_world);
|
||||
/* mul_m4_m3m4 does not transform last row of obmat, i.e. location data. */
|
||||
mul_v3_m3v3(world_and_axes_transform_[3], axes_transform, object->object_to_world[3]);
|
||||
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, world_and_axes_transform_);
|
||||
invert_m3_m3(world_and_axes_normal_transform_, normal_matrix);
|
||||
transpose_m3(world_and_axes_normal_transform_);
|
||||
mirrored_transform_ = is_negative_m3(world_and_axes_normal_transform_);
|
||||
}
|
||||
|
||||
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_object(depsgraph, object);
|
||||
Object export_object_eval_ = dna::shallow_copy(*obj_eval);
|
||||
Mesh *mesh = export_params.apply_modifiers ?
|
||||
BKE_object_get_evaluated_mesh(&export_object_eval_) :
|
||||
BKE_object_get_pre_modified_mesh(&export_object_eval_);
|
||||
|
||||
bool force_triangulation = false;
|
||||
for (const MPoly poly : mesh->polys()) {
|
||||
if (poly.totloop > 255) {
|
||||
force_triangulation = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Triangulate */
|
||||
bool manually_free_mesh = false;
|
||||
if (export_params.export_triangulated_mesh || force_triangulation) {
|
||||
mesh = do_triangulation(mesh, export_params.export_triangulated_mesh);
|
||||
manually_free_mesh = true;
|
||||
}
|
||||
|
||||
const float2 *uv_map = static_cast<const float2 *>(
|
||||
CustomData_get_layer(&mesh->ldata, CD_PROP_FLOAT2));
|
||||
|
||||
Map<UV_vertex_key, int> vertex_map = generate_vertex_map(mesh, uv_map, export_params);
|
||||
|
||||
set_world_axes_transform(
|
||||
&export_object_eval_, export_params.forward_axis, export_params.up_axis);
|
||||
|
||||
/* Load faces into plyData. */
|
||||
int loop_offset = 0;
|
||||
Span<MLoop> loops = mesh->loops();
|
||||
for (const MPoly poly : mesh->polys()) {
|
||||
Span<MLoop> loopSpan = loops.slice(poly.loopstart, poly.totloop);
|
||||
Array<uint32_t> polyVector(loopSpan.size());
|
||||
|
||||
for (int i = 0; i < loopSpan.size(); ++i) {
|
||||
float2 uv;
|
||||
if (export_params.export_uv && uv_map != nullptr) {
|
||||
uv = uv_map[i + loop_offset];
|
||||
}
|
||||
else {
|
||||
uv = {0, 0};
|
||||
}
|
||||
UV_vertex_key key = UV_vertex_key(uv, loopSpan[i].v);
|
||||
int ply_vertex_index = vertex_map.lookup(key);
|
||||
polyVector[i] = (uint32_t(ply_vertex_index + vertex_offset));
|
||||
}
|
||||
loop_offset += loopSpan.size();
|
||||
|
||||
plyData.faces.append(polyVector);
|
||||
}
|
||||
|
||||
Array<int> mesh_vertex_index_LUT(vertex_map.size());
|
||||
Array<int> ply_vertex_index_LUT(mesh->totvert);
|
||||
Array<float2> uv_coordinates(vertex_map.size());
|
||||
|
||||
for (auto const &[key, ply_vertex_index] : vertex_map.items()) {
|
||||
mesh_vertex_index_LUT[ply_vertex_index] = key.mesh_vertex_index;
|
||||
ply_vertex_index_LUT[key.mesh_vertex_index] = ply_vertex_index;
|
||||
uv_coordinates[ply_vertex_index] = key.UV;
|
||||
}
|
||||
|
||||
/* Vertices */
|
||||
for (int i = 0; i < vertex_map.size(); ++i) {
|
||||
float3 r_coords;
|
||||
copy_v3_v3(r_coords, mesh->vert_positions()[mesh_vertex_index_LUT[i]]);
|
||||
mul_m4_v3(world_and_axes_transform_, r_coords);
|
||||
mul_v3_fl(r_coords, export_params.global_scale);
|
||||
plyData.vertices.append(r_coords);
|
||||
}
|
||||
|
||||
/* UV's */
|
||||
if (export_params.export_uv) {
|
||||
for (int i = 0; i < vertex_map.size(); ++i) {
|
||||
plyData.UV_coordinates.append(uv_coordinates[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Normals */
|
||||
if (export_params.export_normals) {
|
||||
const Span<float3> vert_normals = mesh->vert_normals();
|
||||
for (int i = 0; i < vertex_map.size(); i++) {
|
||||
mul_m3_v3(world_and_axes_normal_transform_,
|
||||
float3(vert_normals[mesh_vertex_index_LUT[i]]));
|
||||
plyData.vertex_normals.append(vert_normals[mesh_vertex_index_LUT[i]]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Colors */
|
||||
if (export_params.export_colors) {
|
||||
const StringRef name = mesh->active_color_attribute;
|
||||
if (!name.is_empty()) {
|
||||
const bke::AttributeAccessor attributes = mesh->attributes();
|
||||
const VArray<ColorGeometry4f> color_attribute =
|
||||
attributes.lookup_or_default<ColorGeometry4f>(
|
||||
name, ATTR_DOMAIN_POINT, {0.0f, 0.0f, 0.0f, 0.0f});
|
||||
|
||||
for (int i = 0; i < vertex_map.size(); i++) {
|
||||
ColorGeometry4f colorGeometry = color_attribute[mesh_vertex_index_LUT[i]];
|
||||
float4 vertColor(colorGeometry.r, colorGeometry.g, colorGeometry.b, colorGeometry.a);
|
||||
plyData.vertex_colors.append(vertColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Edges */
|
||||
const bke::LooseEdgeCache &loose_edges = mesh->loose_edges();
|
||||
if (loose_edges.count > 0) {
|
||||
Span<MEdge> edges = mesh->edges();
|
||||
for (int i = 0; i < edges.size(); ++i) {
|
||||
if (loose_edges.is_loose_bits[i]) {
|
||||
int index_one = ply_vertex_index_LUT[edges[i].v1];
|
||||
int index_two = ply_vertex_index_LUT[edges[i].v2];
|
||||
plyData.edges.append({index_one, index_two});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vertex_offset = int(plyData.vertices.size());
|
||||
if (manually_free_mesh) {
|
||||
BKE_id_free(nullptr, mesh);
|
||||
}
|
||||
}
|
||||
|
||||
DEG_OBJECT_ITER_END;
|
||||
}
|
||||
|
||||
Map<UV_vertex_key, int> generate_vertex_map(const Mesh *mesh,
|
||||
const float2 *uv_map,
|
||||
const PLYExportParams &export_params)
|
||||
{
|
||||
|
||||
Map<UV_vertex_key, int> vertex_map;
|
||||
|
||||
const Span<MPoly> polys = mesh->polys();
|
||||
const Span<MLoop> loops = mesh->loops();
|
||||
const int totvert = mesh->totvert;
|
||||
|
||||
vertex_map.reserve(totvert);
|
||||
|
||||
if (uv_map == nullptr || !export_params.export_uv) {
|
||||
for (int vertex_index = 0; vertex_index < totvert; ++vertex_index) {
|
||||
UV_vertex_key key = UV_vertex_key({0, 0}, vertex_index);
|
||||
vertex_map.add_new(key, int(vertex_map.size()));
|
||||
}
|
||||
return vertex_map;
|
||||
}
|
||||
|
||||
const float limit[2] = {STD_UV_CONNECT_LIMIT, STD_UV_CONNECT_LIMIT};
|
||||
UvVertMap *uv_vert_map = BKE_mesh_uv_vert_map_create(polys.data(),
|
||||
nullptr,
|
||||
nullptr,
|
||||
loops.data(),
|
||||
reinterpret_cast<const float(*)[2]>(uv_map),
|
||||
uint(polys.size()),
|
||||
totvert,
|
||||
limit,
|
||||
false,
|
||||
false);
|
||||
|
||||
for (int vertex_index = 0; vertex_index < totvert; vertex_index++) {
|
||||
const UvMapVert *uv_vert = BKE_mesh_uv_vert_map_get_vert(uv_vert_map, vertex_index);
|
||||
|
||||
if (uv_vert == nullptr) {
|
||||
UV_vertex_key key = UV_vertex_key({0, 0}, vertex_index);
|
||||
vertex_map.add_new(key, int(vertex_map.size()));
|
||||
}
|
||||
|
||||
for (; uv_vert; uv_vert = uv_vert->next) {
|
||||
/* Store UV vertex coordinates. */
|
||||
const int loopstart = polys[uv_vert->poly_index].loopstart;
|
||||
float2 vert_uv_coords(uv_map[loopstart + uv_vert->loop_of_poly_index]);
|
||||
UV_vertex_key key = UV_vertex_key(vert_uv_coords, vertex_index);
|
||||
vertex_map.add(key, int(vertex_map.size()));
|
||||
}
|
||||
}
|
||||
BKE_mesh_uv_vert_map_free(uv_vert_map);
|
||||
return vertex_map;
|
||||
}
|
||||
} // namespace blender::io::ply
|
|
@ -0,0 +1,60 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BKE_mesh.h"
|
||||
#include "BLI_math.h"
|
||||
|
||||
#include "BKE_context.h"
|
||||
#include "BKE_mesh.h"
|
||||
#include "BKE_mesh_mapping.h"
|
||||
#include "BKE_object.h"
|
||||
#include "BLI_math.h"
|
||||
|
||||
#include "RNA_types.h"
|
||||
|
||||
#include "DEG_depsgraph.h"
|
||||
#include "DEG_depsgraph_build.h"
|
||||
#include "DEG_depsgraph_query.h"
|
||||
|
||||
#include "DNA_layer_types.h"
|
||||
|
||||
#include "ply_data.hh"
|
||||
|
||||
namespace blender::io::ply {
|
||||
|
||||
Mesh *do_triangulation(const Mesh *mesh, bool force_triangulation);
|
||||
void set_world_axes_transform(Object *object, const eIOAxis forward, const eIOAxis up);
|
||||
|
||||
struct UV_vertex_key {
|
||||
float2 UV;
|
||||
int mesh_vertex_index;
|
||||
|
||||
UV_vertex_key(float2 UV, int vertex_index) : UV(UV), mesh_vertex_index(vertex_index)
|
||||
{
|
||||
}
|
||||
|
||||
bool operator==(const UV_vertex_key &r) const
|
||||
{
|
||||
return (UV == r.UV && mesh_vertex_index == r.mesh_vertex_index);
|
||||
}
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
return ((std::hash<float>()(UV.x) ^ (std::hash<float>()(UV.y) << 1)) >> 1) ^
|
||||
(std::hash<int>()(mesh_vertex_index) << 1);
|
||||
}
|
||||
};
|
||||
|
||||
blender::Map<UV_vertex_key, int> generate_vertex_map(const Mesh *mesh,
|
||||
const float2 *uv_map,
|
||||
const PLYExportParams &export_params);
|
||||
|
||||
void load_plydata(PlyData &plyData, const bContext *C, const PLYExportParams &export_params);
|
||||
|
||||
void load_plydata(PlyData &plyData, Depsgraph *depsgraph, const PLYExportParams &export_params);
|
||||
} // namespace blender::io::ply
|
|
@ -0,0 +1,82 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#include "ply_file_buffer.hh"
|
||||
|
||||
namespace blender::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()
|
||||
{
|
||||
int close_status = std::fclose(outfile_);
|
||||
if (close_status == EOF) {
|
||||
return;
|
||||
}
|
||||
if (outfile_ && close_status) {
|
||||
std::cerr << "Error: could not close the file '" << this->filepath_
|
||||
<< "' properly, it may be corrupted." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
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::ensure_space(size_t at_least)
|
||||
{
|
||||
if (blocks_.is_empty() || (blocks_.last().capacity() - blocks_.last().size() < at_least)) {
|
||||
|
||||
blocks_.append(VectorChar());
|
||||
blocks_.reserve(std::max(at_least, buffer_chunk_size_));
|
||||
}
|
||||
}
|
||||
|
||||
void FileBuffer::write_bytes(Span<char> bytes)
|
||||
{
|
||||
ensure_space(bytes.size());
|
||||
VectorChar &bb = blocks_.last();
|
||||
bb.insert(bb.end(), bytes.begin(), bytes.end());
|
||||
}
|
||||
|
||||
} // namespace blender::io::ply
|
|
@ -0,0 +1,94 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "BLI_array.hh"
|
||||
#include "BLI_compiler_attrs.h"
|
||||
#include "BLI_fileops.h"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_utility_mixins.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
/* SEP macro from BLI path utils clashes with SEP symbol in fmt headers. */
|
||||
#undef SEP
|
||||
#define FMT_HEADER_ONLY
|
||||
#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_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);
|
||||
|
||||
template<typename... T> void write_fstring(const char *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,51 @@
|
|||
/* 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_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,30 @@
|
|||
/* 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_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,66 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#include "ply_file_buffer_binary.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_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,42 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "BLI_array.hh"
|
||||
#include "BLI_compiler_attrs.h"
|
||||
#include "BLI_fileops.h"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_utility_mixins.hh"
|
||||
|
||||
#include "ply_file_buffer.hh"
|
||||
|
||||
#include <bitset>
|
||||
|
||||
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_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
|
|
@ -0,0 +1,211 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#include "BKE_layer.h"
|
||||
#include "BKE_lib_id.h"
|
||||
#include "BKE_mesh.h"
|
||||
#include "BKE_object.h"
|
||||
#include "BKE_report.h"
|
||||
|
||||
#include "DNA_collection_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
#include "DNA_scene_types.h"
|
||||
|
||||
#include "BLI_fileops.hh"
|
||||
#include "BLI_math_vector.h"
|
||||
#include "BLI_memory_utils.hh"
|
||||
|
||||
#include "DEG_depsgraph.h"
|
||||
#include "DEG_depsgraph_build.h"
|
||||
|
||||
#include "ply_data.hh"
|
||||
#include "ply_functions.hh"
|
||||
#include "ply_import.hh"
|
||||
#include "ply_import_ascii.hh"
|
||||
#include "ply_import_binary.hh"
|
||||
#include "ply_import_mesh.hh"
|
||||
|
||||
namespace blender::io::ply {
|
||||
|
||||
void splitstr(std::string str, Vector<std::string> &words, const StringRef &deli)
|
||||
{
|
||||
int pos;
|
||||
|
||||
while ((pos = int(str.find(deli))) != std::string::npos) {
|
||||
words.append(str.substr(0, pos));
|
||||
str.erase(0, pos + deli.size());
|
||||
}
|
||||
/* We add the final word to the vector. */
|
||||
words.append(str.substr());
|
||||
}
|
||||
|
||||
enum PlyDataTypes from_string(const StringRef &input)
|
||||
{
|
||||
if (input == "uchar") {
|
||||
return PlyDataTypes::UCHAR;
|
||||
}
|
||||
if (input == "char") {
|
||||
return PlyDataTypes::CHAR;
|
||||
}
|
||||
if (input == "ushort") {
|
||||
return PlyDataTypes::USHORT;
|
||||
}
|
||||
if (input == "short") {
|
||||
return PlyDataTypes::SHORT;
|
||||
}
|
||||
if (input == "uint") {
|
||||
return PlyDataTypes::UINT;
|
||||
}
|
||||
if (input == "int") {
|
||||
return PlyDataTypes::INT;
|
||||
}
|
||||
if (input == "float") {
|
||||
return PlyDataTypes::FLOAT;
|
||||
}
|
||||
if (input == "double") {
|
||||
return PlyDataTypes::DOUBLE;
|
||||
}
|
||||
return PlyDataTypes::FLOAT;
|
||||
}
|
||||
|
||||
void importer_main(bContext *C, const PLYImportParams &import_params, wmOperator *op)
|
||||
{
|
||||
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, op);
|
||||
}
|
||||
|
||||
void importer_main(Main *bmain,
|
||||
Scene *scene,
|
||||
ViewLayer *view_layer,
|
||||
const PLYImportParams &import_params,
|
||||
wmOperator *op)
|
||||
{
|
||||
|
||||
std::string line;
|
||||
fstream infile(import_params.filepath, std::ios::in | std::ios::binary);
|
||||
|
||||
PlyHeader header;
|
||||
|
||||
while (true) { /* We break when end_header is encountered. */
|
||||
safe_getline(infile, line);
|
||||
if (header.header_size == 0 && line != "ply") {
|
||||
fprintf(stderr, "PLY Importer: failed to read file. Invalid PLY header.\n");
|
||||
BKE_report(op->reports, RPT_ERROR, "PLY Importer: Invalid PLY header.");
|
||||
return;
|
||||
}
|
||||
header.header_size++;
|
||||
Vector<std::string> words{};
|
||||
splitstr(line, words, " ");
|
||||
|
||||
if (strcmp(words[0].c_str(), "format") == 0) {
|
||||
if (strcmp(words[1].c_str(), "ascii") == 0) {
|
||||
header.type = PlyFormatType::ASCII;
|
||||
}
|
||||
else if (strcmp(words[1].c_str(), "binary_big_endian") == 0) {
|
||||
header.type = PlyFormatType::BINARY_BE;
|
||||
}
|
||||
else if (strcmp(words[1].c_str(), "binary_little_endian") == 0) {
|
||||
header.type = PlyFormatType::BINARY_LE;
|
||||
}
|
||||
}
|
||||
else if (strcmp(words[0].c_str(), "element") == 0) {
|
||||
header.elements.append(std::make_pair(words[1], std::stoi(words[2])));
|
||||
if (strcmp(words[1].c_str(), "vertex") == 0) {
|
||||
header.vertex_count = std::stoi(words[2]);
|
||||
}
|
||||
else if (strcmp(words[1].c_str(), "face") == 0) {
|
||||
header.face_count = std::stoi(words[2]);
|
||||
}
|
||||
else if (strcmp(words[1].c_str(), "edge") == 0) {
|
||||
header.edge_count = std::stoi(words[2]);
|
||||
}
|
||||
}
|
||||
else if (strcmp(words[0].c_str(), "property") == 0) {
|
||||
std::pair<std::string, PlyDataTypes> property;
|
||||
property.first = words[2];
|
||||
property.second = from_string(words[1]);
|
||||
|
||||
while (header.properties.size() < header.elements.size()) {
|
||||
Vector<std::pair<std::string, PlyDataTypes>> temp;
|
||||
header.properties.append(temp);
|
||||
}
|
||||
header.properties[header.elements.size() - 1].append(property);
|
||||
}
|
||||
else if (words[0] == "end_header") {
|
||||
break;
|
||||
}
|
||||
else if ((words[0][0] >= '0' && words[0][0] <= '9') || words[0][0] == '-' || line.empty() ||
|
||||
infile.eof()) {
|
||||
/* A value was found before we broke out of the loop. No end_header. */
|
||||
BKE_report(op->reports, RPT_ERROR, "PLY Importer: No end_header");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Name used for both mesh and object. */
|
||||
char ob_name[FILE_MAX];
|
||||
BLI_strncpy(ob_name, BLI_path_basename(import_params.filepath), FILE_MAX);
|
||||
BLI_path_extension_replace(ob_name, FILE_MAX, "");
|
||||
|
||||
Mesh *mesh = BKE_mesh_add(bmain, ob_name);
|
||||
|
||||
BKE_view_layer_base_deselect_all(scene, view_layer);
|
||||
LayerCollection *lc = BKE_layer_collection_get_active(view_layer);
|
||||
Object *obj = BKE_object_add_only_object(bmain, OB_MESH, ob_name);
|
||||
BKE_mesh_assign_object(bmain, obj, mesh);
|
||||
BKE_collection_object_add(bmain, lc->collection, obj);
|
||||
BKE_view_layer_synced_ensure(scene, view_layer);
|
||||
Base *base = BKE_view_layer_base_find(view_layer, obj);
|
||||
BKE_view_layer_base_select_and_set_active(view_layer, base);
|
||||
|
||||
try {
|
||||
std::unique_ptr<PlyData> data;
|
||||
if (header.type == PlyFormatType::ASCII) {
|
||||
data = import_ply_ascii(infile, &header);
|
||||
}
|
||||
else {
|
||||
data = import_ply_binary(infile, &header);
|
||||
}
|
||||
|
||||
Mesh *temp_val = convert_ply_to_mesh(*data, mesh, import_params);
|
||||
if (import_params.merge_verts && temp_val != mesh) {
|
||||
BKE_mesh_nomain_to_mesh(temp_val, mesh, obj);
|
||||
}
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
fprintf(stderr, "PLY Importer: failed to read file. %s.\n", e.what());
|
||||
BKE_report(op->reports, RPT_ERROR, "PLY Importer: failed to parse file.");
|
||||
return;
|
||||
}
|
||||
|
||||
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_COPY_ON_WRITE);
|
||||
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);
|
||||
|
||||
infile.close();
|
||||
}
|
||||
} // namespace blender::io::ply
|
|
@ -0,0 +1,28 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IO_ply.h"
|
||||
#include "ply_data.hh"
|
||||
|
||||
namespace blender::io::ply {
|
||||
|
||||
enum PlyDataTypes from_string(const StringRef &input);
|
||||
|
||||
void splitstr(std::string str, Vector<std::string> &words, const StringRef &deli);
|
||||
|
||||
/* Main import function used from within Blender. */
|
||||
void importer_main(bContext *C, const PLYImportParams &import_params, wmOperator *op);
|
||||
|
||||
/* Used from tests, where full bContext does not exist. */
|
||||
void importer_main(Main *bmain,
|
||||
Scene *scene,
|
||||
ViewLayer *view_layer,
|
||||
const PLYImportParams &import_params,
|
||||
wmOperator *op);
|
||||
|
||||
} // namespace blender::io::ply
|
|
@ -0,0 +1,222 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#include "ply_import_ascii.hh"
|
||||
#include "ply_functions.hh"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
|
||||
namespace blender::io::ply {
|
||||
|
||||
std::unique_ptr<PlyData> import_ply_ascii(fstream &file, PlyHeader *header)
|
||||
{
|
||||
std::unique_ptr<PlyData> data = std::make_unique<PlyData>(load_ply_ascii(file, header));
|
||||
return data;
|
||||
}
|
||||
|
||||
PlyData load_ply_ascii(fstream &file, const PlyHeader *header)
|
||||
{
|
||||
PlyData data;
|
||||
/* Check if header contains alpha. */
|
||||
std::pair<std::string, PlyDataTypes> alpha = {"alpha", PlyDataTypes::UCHAR};
|
||||
bool has_alpha = std::find(header->properties[0].begin(), header->properties[0].end(), alpha) !=
|
||||
header->properties[0].end();
|
||||
|
||||
/* Check if header contains colors. */
|
||||
std::pair<std::string, PlyDataTypes> red = {"red", PlyDataTypes::UCHAR};
|
||||
bool has_color = std::find(header->properties[0].begin(), header->properties[0].end(), red) !=
|
||||
header->properties[0].end();
|
||||
|
||||
/* Check if header contains normals. */
|
||||
std::pair<std::string, PlyDataTypes> normalx = {"nx", PlyDataTypes::FLOAT};
|
||||
bool has_normals = std::find(header->properties[0].begin(),
|
||||
header->properties[0].end(),
|
||||
normalx) != header->properties[0].end();
|
||||
|
||||
/* Check if header contains uv data. */
|
||||
std::pair<std::string, PlyDataTypes> uv = {"s", PlyDataTypes::FLOAT};
|
||||
bool has_UV = std::find(header->properties[0].begin(), header->properties[0].end(), uv) !=
|
||||
header->properties[0].end();
|
||||
|
||||
int3 vertex_index = get_vertex_index(header);
|
||||
int alpha_index;
|
||||
int3 color_index;
|
||||
int3 normal_index;
|
||||
int2 UV_index;
|
||||
|
||||
if (has_alpha) {
|
||||
alpha_index = get_index(header, "alpha", PlyDataTypes::UCHAR);
|
||||
}
|
||||
|
||||
if (has_color) {
|
||||
/* x=red, y=green, z=blue */
|
||||
color_index = get_color_index(header);
|
||||
}
|
||||
|
||||
if (has_normals) {
|
||||
normal_index = get_normal_index(header);
|
||||
}
|
||||
|
||||
if (has_UV) {
|
||||
UV_index = get_uv_index(header);
|
||||
}
|
||||
|
||||
for (int i = 0; i < header->vertex_count; i++) {
|
||||
std::string line;
|
||||
safe_getline(file, line);
|
||||
Vector<std::string> value_vec = explode(line, ' ');
|
||||
|
||||
/* Vertex coords */
|
||||
float3 vertex3;
|
||||
vertex3.x = std::stof(value_vec[vertex_index.x]);
|
||||
vertex3.y = std::stof(value_vec[vertex_index.y]);
|
||||
vertex3.z = std::stof(value_vec[vertex_index.z]);
|
||||
|
||||
data.vertices.append(vertex3);
|
||||
|
||||
/* Vertex colors */
|
||||
if (has_color) {
|
||||
float4 colors4;
|
||||
colors4.x = std::stof(value_vec[color_index.x]) / 255.0f;
|
||||
colors4.y = std::stof(value_vec[color_index.y]) / 255.0f;
|
||||
colors4.z = std::stof(value_vec[color_index.z]) / 255.0f;
|
||||
if (has_alpha) {
|
||||
colors4.w = std::stof(value_vec[alpha_index]) / 255.0f;
|
||||
}
|
||||
else {
|
||||
colors4.w = 1.0f;
|
||||
}
|
||||
|
||||
data.vertex_colors.append(colors4);
|
||||
}
|
||||
|
||||
/* If normals */
|
||||
if (has_normals) {
|
||||
float3 normals3;
|
||||
normals3.x = std::stof(value_vec[normal_index.x]);
|
||||
normals3.y = std::stof(value_vec[normal_index.y]);
|
||||
normals3.z = std::stof(value_vec[normal_index.z]);
|
||||
|
||||
data.vertex_normals.append(normals3);
|
||||
}
|
||||
|
||||
/* If uv */
|
||||
if (has_UV) {
|
||||
float2 uvmap;
|
||||
uvmap.x = std::stof(value_vec[UV_index.x]);
|
||||
uvmap.y = std::stof(value_vec[UV_index.y]);
|
||||
|
||||
data.UV_coordinates.append(uvmap);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < header->face_count; i++) {
|
||||
std::string line;
|
||||
getline(file, line);
|
||||
Vector<std::string> value_vec = explode(line, ' ');
|
||||
int count = std::stoi(value_vec[0]);
|
||||
Array<uint> vertex_indices(count);
|
||||
|
||||
for (int j = 1; j <= count; j++) {
|
||||
int index = std::stoi(value_vec[j]);
|
||||
/* If the face has a vertex index that is outside the range. */
|
||||
if (index >= data.vertices.size()) {
|
||||
throw std::runtime_error("Vertex index out of bounds");
|
||||
}
|
||||
vertex_indices[j - 1] = index;
|
||||
}
|
||||
data.faces.append(vertex_indices);
|
||||
}
|
||||
|
||||
for (int i = 0; i < header->edge_count; i++) {
|
||||
std::string line;
|
||||
getline(file, line);
|
||||
Vector<std::string> value_vec = explode(line, ' ');
|
||||
|
||||
std::pair<int, int> edge = std::make_pair(stoi(value_vec[0]), stoi(value_vec[1]));
|
||||
data.edges.append(edge);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
int3 get_vertex_index(const PlyHeader *header)
|
||||
{
|
||||
int3 vertex_index;
|
||||
vertex_index.x = get_index(header, "x", PlyDataTypes::FLOAT);
|
||||
vertex_index.y = get_index(header, "y", PlyDataTypes::FLOAT);
|
||||
vertex_index.z = get_index(header, "z", PlyDataTypes::FLOAT);
|
||||
|
||||
return vertex_index;
|
||||
}
|
||||
|
||||
int3 get_color_index(const PlyHeader *header)
|
||||
{
|
||||
int3 color_index;
|
||||
color_index.x = get_index(header, "red", PlyDataTypes::UCHAR);
|
||||
color_index.y = get_index(header, "green", PlyDataTypes::UCHAR);
|
||||
color_index.z = get_index(header, "blue", PlyDataTypes::UCHAR);
|
||||
|
||||
return color_index;
|
||||
}
|
||||
|
||||
int3 get_normal_index(const PlyHeader *header)
|
||||
{
|
||||
int3 normal_index;
|
||||
normal_index.x = get_index(header, "nx", PlyDataTypes::FLOAT);
|
||||
normal_index.y = get_index(header, "ny", PlyDataTypes::FLOAT);
|
||||
normal_index.z = get_index(header, "nz", PlyDataTypes::FLOAT);
|
||||
|
||||
return normal_index;
|
||||
}
|
||||
|
||||
int2 get_uv_index(const PlyHeader *header)
|
||||
{
|
||||
int2 uv_index;
|
||||
uv_index.x = get_index(header, "s", PlyDataTypes::FLOAT);
|
||||
uv_index.y = get_index(header, "t", PlyDataTypes::FLOAT);
|
||||
|
||||
return uv_index;
|
||||
}
|
||||
|
||||
int get_index(const PlyHeader *header, std::string property, PlyDataTypes datatype)
|
||||
{
|
||||
std::pair<std::string, PlyDataTypes> pair = {property, datatype};
|
||||
const std::pair<std::string, blender::io::ply::PlyDataTypes> *it = std::find(
|
||||
header->properties[0].begin(), header->properties[0].end(), pair);
|
||||
return (int)(it - header->properties[0].begin());
|
||||
}
|
||||
|
||||
Vector<std::string> explode(const StringRef str, const char &ch)
|
||||
{
|
||||
std::string next;
|
||||
Vector<std::string> result;
|
||||
|
||||
/* For each character in the string. */
|
||||
for (char c : str) {
|
||||
/* If we've hit the terminal character. */
|
||||
if (c == ch) {
|
||||
/* If we have some characters accumulated. */
|
||||
if (!next.empty()) {
|
||||
/* Add them to the result vector. */
|
||||
result.append(next);
|
||||
next.clear();
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Accumulate the next character into the sequence. */
|
||||
next += c;
|
||||
}
|
||||
}
|
||||
|
||||
if (!next.empty()) {
|
||||
result.append(next);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace blender::io::ply
|
|
@ -0,0 +1,39 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_fileops.hh"
|
||||
|
||||
#include "DNA_mesh_types.h"
|
||||
|
||||
#include "IO_ply.h"
|
||||
#include "ply_data.hh"
|
||||
|
||||
namespace blender::io::ply {
|
||||
|
||||
/**
|
||||
* The function that gets called from the importer.
|
||||
* \param file: The PLY file that was opened.
|
||||
* \param header: The information in the PLY header.
|
||||
*/
|
||||
std::unique_ptr<PlyData> import_ply_ascii(fstream &file, PlyHeader *header);
|
||||
|
||||
/**
|
||||
* Loads the information from the PLY file in ASCII format to the PlyData datastructure.
|
||||
* \param file: The PLY file that was opened.
|
||||
* \param header: The information in the PLY header.
|
||||
* \return The PlyData datastructure that can be used for conversion to a Mesh.
|
||||
*/
|
||||
PlyData load_ply_ascii(fstream &file, const PlyHeader *header);
|
||||
|
||||
int3 get_vertex_index(const PlyHeader *header);
|
||||
int3 get_color_index(const PlyHeader *header);
|
||||
int3 get_normal_index(const PlyHeader *header);
|
||||
int2 get_uv_index(const PlyHeader *header);
|
||||
int get_index(const PlyHeader *header, std::string property, PlyDataTypes datatype);
|
||||
Vector<std::string> explode(const StringRef str, const char &ch);
|
||||
} // namespace blender::io::ply
|
|
@ -0,0 +1,215 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
#include "BLI_array.hh"
|
||||
|
||||
#include "ply_import_binary.hh"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
namespace blender::io::ply {
|
||||
std::unique_ptr<PlyData> import_ply_binary(fstream &file, const PlyHeader *header)
|
||||
{
|
||||
std::unique_ptr<PlyData> data = std::make_unique<PlyData>(load_ply_binary(file, header));
|
||||
return data;
|
||||
}
|
||||
|
||||
template<typename T> T read(fstream &file, bool isBigEndian)
|
||||
{
|
||||
T returnVal;
|
||||
file.read((char *)&returnVal, sizeof(returnVal));
|
||||
check_file_errors(file);
|
||||
if (isBigEndian) {
|
||||
returnVal = swap_bytes<T>(returnVal);
|
||||
}
|
||||
return returnVal;
|
||||
}
|
||||
|
||||
template uint8_t read<uint8_t>(fstream &file, bool isBigEndian);
|
||||
template int8_t read<int8_t>(fstream &file, bool isBigEndian);
|
||||
template uint16_t read<uint16_t>(fstream &file, bool isBigEndian);
|
||||
template int16_t read<int16_t>(fstream &file, bool isBigEndian);
|
||||
template uint32_t read<uint32_t>(fstream &file, bool isBigEndian);
|
||||
template int32_t read<int32_t>(fstream &file, bool isBigEndian);
|
||||
template float read<float>(fstream &file, bool isBigEndian);
|
||||
template double read<double>(fstream &file, bool isBigEndian);
|
||||
|
||||
void check_file_errors(const fstream &file)
|
||||
{
|
||||
if (file.bad()) {
|
||||
throw std::ios_base::failure("Read/Write error on io operation");
|
||||
}
|
||||
if (file.fail()) {
|
||||
throw std::ios_base::failure("Logical error on io operation");
|
||||
}
|
||||
if (file.eof()) {
|
||||
throw std::ios_base::failure("Reached end of the file");
|
||||
}
|
||||
}
|
||||
|
||||
void discard_value(fstream &file, const PlyDataTypes type)
|
||||
{
|
||||
switch (type) {
|
||||
case CHAR:
|
||||
read<int8_t>(file, false);
|
||||
break;
|
||||
case UCHAR:
|
||||
read<uint8_t>(file, false);
|
||||
break;
|
||||
case SHORT:
|
||||
read<int16_t>(file, false);
|
||||
break;
|
||||
case USHORT:
|
||||
read<uint16_t>(file, false);
|
||||
break;
|
||||
case INT:
|
||||
read<int32_t>(file, false);
|
||||
break;
|
||||
case UINT:
|
||||
read<uint32_t>(file, false);
|
||||
break;
|
||||
case FLOAT:
|
||||
read<float>(file, false);
|
||||
break;
|
||||
case DOUBLE:
|
||||
read<double>(file, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
PlyData load_ply_binary(fstream &file, const PlyHeader *header)
|
||||
{
|
||||
PlyData data;
|
||||
bool isBigEndian = header->type == PlyFormatType::BINARY_BE;
|
||||
|
||||
for (int i = 0; i < header->elements.size(); i++) {
|
||||
if (header->elements[i].first == "vertex") {
|
||||
/* Import vertices. */
|
||||
load_vertex_data(file, header, &data, i);
|
||||
}
|
||||
else if (header->elements[i].first == "edge") {
|
||||
/* Import edges. */
|
||||
for (int j = 0; j < header->elements[i].second; j++) {
|
||||
std::pair<int, int> vertex_indices;
|
||||
for (auto [name, type] : header->properties[i]) {
|
||||
if (name == "vertex1") {
|
||||
vertex_indices.first = int(read<int32_t>(file, isBigEndian));
|
||||
}
|
||||
else if (name == "vertex2") {
|
||||
vertex_indices.second = int(read<int32_t>(file, isBigEndian));
|
||||
}
|
||||
else {
|
||||
discard_value(file, type);
|
||||
}
|
||||
}
|
||||
data.edges.append(vertex_indices);
|
||||
}
|
||||
}
|
||||
else if (header->elements[i].first == "face") {
|
||||
|
||||
/* Import faces. */
|
||||
for (int j = 0; j < header->elements[i].second; j++) {
|
||||
/* Assume vertex_index_count_type is uchar. */
|
||||
uint8_t count = read<uint8_t>(file, isBigEndian);
|
||||
Array<uint> vertex_indices(count);
|
||||
|
||||
/* Loop over the amount of vertex indices in this face. */
|
||||
for (uint8_t k = 0; k < count; k++) {
|
||||
uint32_t index = read<uint32_t>(file, isBigEndian);
|
||||
/* If the face has a vertex index that is outside the range. */
|
||||
if (index >= data.vertices.size()) {
|
||||
throw std::runtime_error("Vertex index out of bounds");
|
||||
}
|
||||
vertex_indices[k] = index;
|
||||
}
|
||||
data.faces.append(vertex_indices);
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Nothing else is supported. */
|
||||
for (int j = 0; j < header->elements[i].second; j++) {
|
||||
for (auto [name, type] : header->properties[i]) {
|
||||
discard_value(file, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void load_vertex_data(fstream &file, const PlyHeader *header, PlyData *r_data, int index)
|
||||
{
|
||||
bool hasNormal = false;
|
||||
bool hasColor = false;
|
||||
bool hasUv = false;
|
||||
bool isBigEndian = header->type == PlyFormatType::BINARY_BE;
|
||||
|
||||
for (int i = 0; i < header->vertex_count; i++) {
|
||||
float3 coord{0};
|
||||
float3 normal{0};
|
||||
float4 color{1};
|
||||
float2 uv{0};
|
||||
|
||||
for (auto [name, type] : header->properties[index]) {
|
||||
if (name == "x") {
|
||||
coord.x = read<float>(file, isBigEndian);
|
||||
}
|
||||
else if (name == "y") {
|
||||
coord.y = read<float>(file, isBigEndian);
|
||||
}
|
||||
else if (name == "z") {
|
||||
coord.z = read<float>(file, isBigEndian);
|
||||
}
|
||||
else if (name == "nx") {
|
||||
normal.x = read<float>(file, isBigEndian);
|
||||
hasNormal = true;
|
||||
}
|
||||
else if (name == "ny") {
|
||||
normal.y = read<float>(file, isBigEndian);
|
||||
}
|
||||
else if (name == "nz") {
|
||||
normal.z = read<float>(file, isBigEndian);
|
||||
}
|
||||
else if (name == "red") {
|
||||
color.x = read<uint8_t>(file, isBigEndian) / 255.0f;
|
||||
hasColor = true;
|
||||
}
|
||||
else if (name == "green") {
|
||||
color.y = read<uint8_t>(file, isBigEndian) / 255.0f;
|
||||
}
|
||||
else if (name == "blue") {
|
||||
color.z = read<uint8_t>(file, isBigEndian) / 255.0f;
|
||||
}
|
||||
else if (name == "alpha") {
|
||||
color.w = read<uint8_t>(file, isBigEndian) / 255.0f;
|
||||
}
|
||||
else if (name == "s") {
|
||||
uv.x = read<float>(file, isBigEndian);
|
||||
hasUv = true;
|
||||
}
|
||||
else if (name == "t") {
|
||||
uv.y = read<float>(file, isBigEndian);
|
||||
}
|
||||
else {
|
||||
/* No other properties are supported yet. */
|
||||
discard_value(file, type);
|
||||
}
|
||||
}
|
||||
|
||||
r_data->vertices.append(coord);
|
||||
if (hasNormal) {
|
||||
r_data->vertex_normals.append(normal);
|
||||
}
|
||||
if (hasColor) {
|
||||
r_data->vertex_colors.append(color);
|
||||
}
|
||||
if (hasUv) {
|
||||
r_data->UV_coordinates.append(uv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::io::ply
|
|
@ -0,0 +1,71 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_endian_switch.h"
|
||||
#include "BLI_fileops.hh"
|
||||
|
||||
#include "ply_data.hh"
|
||||
|
||||
namespace blender::io::ply {
|
||||
|
||||
/**
|
||||
* The function that gets called from the importer.
|
||||
* \param file: The PLY file that was opened.
|
||||
* \param header: The information in the PLY header.
|
||||
* \return The PlyData datastructure that can be used for conversion to a Mesh.
|
||||
*/
|
||||
std::unique_ptr<PlyData> import_ply_binary(fstream &file, const PlyHeader *header);
|
||||
|
||||
/**
|
||||
* Loads the information from the PLY file in binary format to the PlyData datastructure.
|
||||
* \param file: The PLY file that was opened.
|
||||
* \param header: The information in the PLY header.
|
||||
* \return The PlyData datastructure that can be used for conversion to a Mesh.
|
||||
*/
|
||||
PlyData load_ply_binary(fstream &file, const PlyHeader *header);
|
||||
|
||||
void load_vertex_data(fstream &file, const PlyHeader *header, PlyData *r_data, int index);
|
||||
|
||||
void check_file_errors(const fstream &file);
|
||||
|
||||
void discard_value(fstream &file, const PlyDataTypes type);
|
||||
|
||||
template<typename T> T swap_bytes(T input)
|
||||
{
|
||||
/* In big endian, the most-significant byte is first.
|
||||
* So, we need to swap the byte order. */
|
||||
|
||||
/* 0xAC in LE should become 0xCA in BE. */
|
||||
if (sizeof(T) == 1) {
|
||||
return input;
|
||||
}
|
||||
|
||||
if constexpr (sizeof(T) == 2) {
|
||||
uint16_t value = reinterpret_cast<uint16_t &>(input);
|
||||
BLI_endian_switch_uint16(&value);
|
||||
return reinterpret_cast<T &>(value);
|
||||
}
|
||||
|
||||
if constexpr (sizeof(T) == 4) {
|
||||
/* Reinterpret this data as uint32 for easy rearranging of bytes. */
|
||||
uint32_t value = reinterpret_cast<uint32_t &>(input);
|
||||
BLI_endian_switch_uint32(&value);
|
||||
return reinterpret_cast<T &>(value);
|
||||
}
|
||||
|
||||
if constexpr (sizeof(T) == 8) {
|
||||
/* Reinterpret this data as uint64 for easy rearranging of bytes. */
|
||||
uint64_t value = reinterpret_cast<uint64_t &>(input);
|
||||
BLI_endian_switch_uint64(&value);
|
||||
return reinterpret_cast<T &>(value);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T> T read(fstream &file, bool isBigEndian);
|
||||
|
||||
} // namespace blender::io::ply
|
|
@ -0,0 +1,117 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#include "BKE_attribute.h"
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_customdata.h"
|
||||
#include "BKE_mesh.h"
|
||||
#include "BKE_mesh_runtime.h"
|
||||
|
||||
#include "GEO_mesh_merge_by_distance.hh"
|
||||
|
||||
#include "BLI_math_vector.h"
|
||||
|
||||
#include "ply_import_mesh.hh"
|
||||
|
||||
namespace blender::io::ply {
|
||||
Mesh *convert_ply_to_mesh(PlyData &data, Mesh *mesh, const PLYImportParams ¶ms)
|
||||
{
|
||||
|
||||
/* Add vertices to the mesh. */
|
||||
mesh->totvert = int(data.vertices.size());
|
||||
CustomData_add_layer_named(
|
||||
&mesh->vdata, CD_PROP_FLOAT3, CD_CONSTRUCT, nullptr, mesh->totvert, "position");
|
||||
mesh->vert_positions_for_write().copy_from(data.vertices);
|
||||
|
||||
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
|
||||
|
||||
if (!data.edges.is_empty()) {
|
||||
mesh->totedge = int(data.edges.size());
|
||||
CustomData_add_layer(&mesh->edata, CD_MEDGE, CD_SET_DEFAULT, nullptr, mesh->totedge);
|
||||
MutableSpan<MEdge> edges = mesh->edges_for_write();
|
||||
for (int i = 0; i < mesh->totedge; i++) {
|
||||
edges[i].v1 = data.edges[i].first;
|
||||
edges[i].v2 = data.edges[i].second;
|
||||
}
|
||||
}
|
||||
|
||||
/* Add faces to the mesh. */
|
||||
if (!data.faces.is_empty()) {
|
||||
/* Specify amount of total faces. */
|
||||
mesh->totpoly = int(data.faces.size());
|
||||
mesh->totloop = 0;
|
||||
for (int i = 0; i < data.faces.size(); i++) {
|
||||
/* Add number of loops from the vertex indices in the face. */
|
||||
mesh->totloop += data.faces[i].size();
|
||||
}
|
||||
CustomData_add_layer(&mesh->pdata, CD_MPOLY, CD_SET_DEFAULT, nullptr, mesh->totpoly);
|
||||
CustomData_add_layer(&mesh->ldata, CD_MLOOP, CD_SET_DEFAULT, nullptr, mesh->totloop);
|
||||
MutableSpan<MPoly> polys = mesh->polys_for_write();
|
||||
MutableSpan<MLoop> loops = mesh->loops_for_write();
|
||||
|
||||
int offset = 0;
|
||||
/* Iterate over amount of faces. */
|
||||
for (int i = 0; i < mesh->totpoly; i++) {
|
||||
int size = int(data.faces[i].size());
|
||||
/* Set the index from where this face starts and specify the amount of edges it has. */
|
||||
polys[i].loopstart = offset;
|
||||
polys[i].totloop = size;
|
||||
|
||||
for (int j = 0; j < size; j++) {
|
||||
/* Set the vertex index of the loop to the one in PlyData. */
|
||||
loops[offset + j].v = data.faces[i][j];
|
||||
}
|
||||
offset += size;
|
||||
}
|
||||
}
|
||||
|
||||
/* Vertex colors */
|
||||
if (!data.vertex_colors.is_empty()) {
|
||||
/* Create a data layer for vertex colors and set them. */
|
||||
bke::SpanAttributeWriter<ColorGeometry4f> colors =
|
||||
attributes.lookup_or_add_for_write_span<ColorGeometry4f>("Col", ATTR_DOMAIN_POINT);
|
||||
for (int i = 0; i < data.vertex_colors.size(); i++) {
|
||||
copy_v4_v4(colors.span[i], data.vertex_colors[i]);
|
||||
}
|
||||
colors.finish();
|
||||
BKE_id_attributes_active_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", ATTR_DOMAIN_CORNER);
|
||||
int counter = 0;
|
||||
for (int i = 0; i < data.faces.size(); i++) {
|
||||
for (int j = 0; j < data.faces[i].size(); j++) {
|
||||
uv_map.span[counter] = data.UV_coordinates[data.faces[i][j]];
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
uv_map.finish();
|
||||
}
|
||||
|
||||
/* Calculate edges from the rest of the mesh. */
|
||||
BKE_mesh_calc_edges(mesh, true, false);
|
||||
|
||||
/* Note: This is important to do after initializing the loops. */
|
||||
if (!data.vertex_normals.is_empty()) {
|
||||
BKE_mesh_set_custom_normals_from_verts(
|
||||
mesh, reinterpret_cast<float(*)[3]>(data.vertex_normals.data()));
|
||||
}
|
||||
|
||||
/* Merge all vertices on the same location. */
|
||||
if (params.merge_verts) {
|
||||
std::optional<Mesh *> return_value = blender::geometry::mesh_merge_by_distance_all(
|
||||
*mesh, IndexMask(mesh->totvert), 0.0001f);
|
||||
if (return_value.has_value()) {
|
||||
mesh = return_value.value();
|
||||
}
|
||||
}
|
||||
|
||||
return mesh;
|
||||
}
|
||||
} // namespace blender::io::ply
|
|
@ -0,0 +1,21 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IO_ply.h"
|
||||
#include "ply_data.hh"
|
||||
|
||||
namespace blender::io::ply {
|
||||
|
||||
/**
|
||||
* Converts the PlyData datastructure to a mesh.
|
||||
* \param data: The PLY data.
|
||||
* \return The mesh that can be used inside blender.
|
||||
*/
|
||||
Mesh *convert_ply_to_mesh(PlyData &data, Mesh *mesh, const PLYImportParams ¶ms);
|
||||
|
||||
} // namespace blender::io::ply
|
|
@ -0,0 +1,42 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_array.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
namespace blender::io::ply {
|
||||
|
||||
enum PlyDataTypes { CHAR, UCHAR, SHORT, USHORT, INT, UINT, FLOAT, DOUBLE };
|
||||
|
||||
struct PlyData {
|
||||
Vector<float3> vertices;
|
||||
Vector<float3> vertex_normals;
|
||||
/* Value between 0 and 1. */
|
||||
Vector<float4> vertex_colors;
|
||||
Vector<std::pair<int, int>> edges;
|
||||
Vector<float3> edge_colors;
|
||||
Vector<Array<uint32_t>> faces;
|
||||
Vector<float2> UV_coordinates;
|
||||
};
|
||||
|
||||
enum PlyFormatType { ASCII, BINARY_LE, BINARY_BE };
|
||||
|
||||
struct PlyHeader {
|
||||
int vertex_count = 0;
|
||||
int edge_count = 0;
|
||||
int face_count = 0;
|
||||
int header_size = 0;
|
||||
/* List of elements in ply file with their count. */
|
||||
Vector<std::pair<std::string, int>> elements;
|
||||
/* List of properties (Name, type) per element. */
|
||||
Vector<Vector<std::pair<std::string, PlyDataTypes>>> properties;
|
||||
PlyFormatType type;
|
||||
};
|
||||
|
||||
} // namespace blender::io::ply
|
|
@ -0,0 +1,51 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#include "ply_functions.hh"
|
||||
|
||||
namespace blender::io::ply {
|
||||
|
||||
line_ending safe_getline(fstream &file, std::string &line)
|
||||
{
|
||||
line.clear();
|
||||
std::streambuf *sb = file.rdbuf();
|
||||
std::istream::sentry se(file, true);
|
||||
|
||||
line_ending possible = UNSET;
|
||||
char c;
|
||||
while (sb->sgetc() != std::streambuf::traits_type::eof()) {
|
||||
c = char(sb->sgetc());
|
||||
switch (c) {
|
||||
case '\n':
|
||||
if (possible == UNSET) {
|
||||
possible = LF;
|
||||
}
|
||||
else if (possible == CR) {
|
||||
possible = CR_LF;
|
||||
}
|
||||
break;
|
||||
case '\r':
|
||||
if (possible == UNSET) {
|
||||
possible = CR;
|
||||
}
|
||||
else if (possible == LF) {
|
||||
possible = LF_CR;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* If a different character is encountered after the line ending is set, we know to return.
|
||||
*/
|
||||
if (possible != UNSET) {
|
||||
return possible;
|
||||
}
|
||||
line += c;
|
||||
break;
|
||||
}
|
||||
sb->sbumpc();
|
||||
}
|
||||
return possible;
|
||||
}
|
||||
} // namespace blender::io::ply
|
|
@ -0,0 +1,26 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup ply
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_fileops.hh"
|
||||
#include <string>
|
||||
|
||||
namespace blender::io::ply {
|
||||
|
||||
enum line_ending { CR_LF, LF, CR, LF_CR, UNSET };
|
||||
|
||||
/**
|
||||
* Reads a line in the ply file in a line-ending safe manner. All different line endings are
|
||||
* supported. This also supports a mix of different line endings in the same file. CR (\\r), LF
|
||||
* (\\n), CR/LF (\\r\\n), LF/CR (\\n\\r).
|
||||
* \param file: The file stream.
|
||||
* \param line: The string you want to read to.
|
||||
* \return The line ending enum if you're interested.
|
||||
*/
|
||||
line_ending safe_getline(fstream &file, std::string &line);
|
||||
|
||||
} // namespace blender::io::ply
|
|
@ -0,0 +1,463 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "testing/testing.h"
|
||||
#include "tests/blendfile_loading_base_test.h"
|
||||
|
||||
#include "BKE_blender_version.h"
|
||||
|
||||
#include "DEG_depsgraph.h"
|
||||
|
||||
#include "IO_ply.h"
|
||||
#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;
|
||||
}
|
||||
};
|
||||
|
||||
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->faces = {
|
||||
{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;
|
||||
void *buffer = BLI_file_read_text_as_mem(file_path.c_str(), 0, &buffer_len);
|
||||
if (buffer != nullptr) {
|
||||
res.assign((const char *)buffer, buffer_len);
|
||||
MEM_freeN(buffer);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
char read(std::ifstream &file)
|
||||
{
|
||||
char returnVal;
|
||||
file.read((char *)&returnVal, sizeof(returnVal));
|
||||
return returnVal;
|
||||
}
|
||||
|
||||
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 = blender::tests::flags_test_release_dir() + "/" + temp_file_path;
|
||||
PLYExportParams _params;
|
||||
_params.ascii_format = true;
|
||||
_params.export_normals = false;
|
||||
_params.export_colors = false;
|
||||
BLI_strncpy(_params.filepath, filePath.c_str(), 1024);
|
||||
|
||||
std::unique_ptr<PlyData> plyData = load_cube(_params);
|
||||
|
||||
std::unique_ptr<FileBuffer> buffer = std::make_unique<FileBufferAscii>(_params.filepath);
|
||||
|
||||
write_header(*buffer.get(), *plyData.get(), _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 = blender::tests::flags_test_release_dir() + "/" + temp_file_path;
|
||||
PLYExportParams _params;
|
||||
_params.ascii_format = false;
|
||||
_params.export_normals = false;
|
||||
_params.export_colors = false;
|
||||
BLI_strncpy(_params.filepath, filePath.c_str(), 1024);
|
||||
|
||||
std::unique_ptr<PlyData> plyData = load_cube(_params);
|
||||
|
||||
std::unique_ptr<FileBuffer> buffer = std::make_unique<FileBufferBinary>(_params.filepath);
|
||||
|
||||
write_header(*buffer.get(), *plyData.get(), _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 = blender::tests::flags_test_release_dir() + "/" + temp_file_path;
|
||||
PLYExportParams _params;
|
||||
_params.ascii_format = true;
|
||||
_params.export_normals = false;
|
||||
_params.export_colors = false;
|
||||
BLI_strncpy(_params.filepath, filePath.c_str(), 1024);
|
||||
|
||||
std::unique_ptr<PlyData> plyData = load_cube(_params);
|
||||
|
||||
std::unique_ptr<FileBuffer> buffer = std::make_unique<FileBufferAscii>(_params.filepath);
|
||||
|
||||
write_vertices(*buffer.get(), *plyData.get());
|
||||
|
||||
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 = blender::tests::flags_test_release_dir() + "/" + temp_file_path;
|
||||
PLYExportParams _params;
|
||||
_params.ascii_format = false;
|
||||
_params.export_normals = false;
|
||||
_params.export_colors = false;
|
||||
BLI_strncpy(_params.filepath, filePath.c_str(), 1024);
|
||||
|
||||
std::unique_ptr<PlyData> plyData = load_cube(_params);
|
||||
|
||||
std::unique_ptr<FileBuffer> buffer = std::make_unique<FileBufferBinary>(_params.filepath);
|
||||
|
||||
write_vertices(*buffer.get(), *plyData.get());
|
||||
|
||||
buffer->close_file();
|
||||
|
||||
std::vector<char> result = read_temp_file_in_vectorchar(filePath);
|
||||
|
||||
std::vector<char> expected(
|
||||
{(char)0x62, (char)0xA0, (char)0x8F, (char)0x3F, (char)0x62, (char)0xA0, (char)0x8F,
|
||||
(char)0x3F, (char)0x62, (char)0xA0, (char)0x8F, (char)0x3F, (char)0x62, (char)0xA0,
|
||||
(char)0x8F, (char)0x3F, (char)0x62, (char)0xA0, (char)0x8F, (char)0x3F, (char)0x62,
|
||||
(char)0xA0, (char)0x8F, (char)0xBF, (char)0x62, (char)0xA0, (char)0x8F, (char)0x3F,
|
||||
(char)0x62, (char)0xA0, (char)0x8F, (char)0xBF, (char)0x62, (char)0xA0, (char)0x8F,
|
||||
(char)0x3F, (char)0x62, (char)0xA0, (char)0x8F, (char)0x3F, (char)0x62, (char)0xA0,
|
||||
(char)0x8F, (char)0xBF, (char)0x62, (char)0xA0, (char)0x8F, (char)0xBF, (char)0x62,
|
||||
(char)0xA0, (char)0x8F, (char)0xBF, (char)0x62, (char)0xA0, (char)0x8F, (char)0x3F,
|
||||
(char)0x62, (char)0xA0, (char)0x8F, (char)0x3F, (char)0x62, (char)0xA0, (char)0x8F,
|
||||
(char)0xBF, (char)0x62, (char)0xA0, (char)0x8F, (char)0x3F, (char)0x62, (char)0xA0,
|
||||
(char)0x8F, (char)0xBF, (char)0x62, (char)0xA0, (char)0x8F, (char)0xBF, (char)0x62,
|
||||
(char)0xA0, (char)0x8F, (char)0xBF, (char)0x62, (char)0xA0, (char)0x8F, (char)0x3F,
|
||||
(char)0x62, (char)0xA0, (char)0x8F, (char)0xBF, (char)0x62, (char)0xA0, (char)0x8F,
|
||||
(char)0xBF, (char)0x62, (char)0xA0, (char)0x8F, (char)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 = blender::tests::flags_test_release_dir() + "/" + temp_file_path;
|
||||
PLYExportParams _params;
|
||||
_params.ascii_format = true;
|
||||
_params.export_normals = false;
|
||||
_params.export_colors = false;
|
||||
BLI_strncpy(_params.filepath, filePath.c_str(), 1024);
|
||||
|
||||
std::unique_ptr<PlyData> plyData = load_cube(_params);
|
||||
|
||||
std::unique_ptr<FileBuffer> buffer = std::make_unique<FileBufferAscii>(_params.filepath);
|
||||
|
||||
write_faces(*buffer.get(), *plyData.get());
|
||||
|
||||
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 = blender::tests::flags_test_release_dir() + "/" + temp_file_path;
|
||||
PLYExportParams _params;
|
||||
_params.ascii_format = false;
|
||||
_params.export_normals = false;
|
||||
_params.export_colors = false;
|
||||
BLI_strncpy(_params.filepath, filePath.c_str(), 1024);
|
||||
|
||||
std::unique_ptr<PlyData> plyData = load_cube(_params);
|
||||
|
||||
std::unique_ptr<FileBuffer> buffer = std::make_unique<FileBufferBinary>(_params.filepath);
|
||||
|
||||
write_faces(*buffer.get(), *plyData.get());
|
||||
|
||||
buffer->close_file();
|
||||
|
||||
std::vector<char> result = read_temp_file_in_vectorchar(filePath);
|
||||
|
||||
std::vector<char> expected(
|
||||
{(char)0x04, (char)0x00, (char)0x00, (char)0x00, (char)0x00, (char)0x02, (char)0x00,
|
||||
(char)0x00, (char)0x00, (char)0x06, (char)0x00, (char)0x00, (char)0x00, (char)0x04,
|
||||
(char)0x00, (char)0x00, (char)0x00, (char)0x04, (char)0x03, (char)0x00, (char)0x00,
|
||||
(char)0x00, (char)0x07, (char)0x00, (char)0x00, (char)0x00, (char)0x06, (char)0x00,
|
||||
(char)0x00, (char)0x00, (char)0x02, (char)0x00, (char)0x00, (char)0x00, (char)0x04,
|
||||
(char)0x07, (char)0x00, (char)0x00, (char)0x00, (char)0x05, (char)0x00, (char)0x00,
|
||||
(char)0x00, (char)0x04, (char)0x00, (char)0x00, (char)0x00, (char)0x06, (char)0x00,
|
||||
(char)0x00, (char)0x00, (char)0x04, (char)0x05, (char)0x00, (char)0x00, (char)0x00,
|
||||
(char)0x07, (char)0x00, (char)0x00, (char)0x00, (char)0x03, (char)0x00, (char)0x00,
|
||||
(char)0x00, (char)0x01, (char)0x00, (char)0x00, (char)0x00, (char)0x04, (char)0x01,
|
||||
(char)0x00, (char)0x00, (char)0x00, (char)0x03, (char)0x00, (char)0x00, (char)0x00,
|
||||
(char)0x02, (char)0x00, (char)0x00, (char)0x00, (char)0x00, (char)0x00, (char)0x00,
|
||||
(char)0x00, (char)0x04, (char)0x05, (char)0x00, (char)0x00, (char)0x00, (char)0x01,
|
||||
(char)0x00, (char)0x00, (char)0x00, (char)0x00, (char)0x00, (char)0x00, (char)0x00,
|
||||
(char)0x04, (char)0x00, (char)0x00, (char)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 = blender::tests::flags_test_release_dir() + "/" + temp_file_path;
|
||||
PLYExportParams _params;
|
||||
_params.ascii_format = true;
|
||||
_params.export_normals = true;
|
||||
_params.export_colors = false;
|
||||
BLI_strncpy(_params.filepath, filePath.c_str(), 1024);
|
||||
|
||||
std::unique_ptr<PlyData> plyData = load_cube(_params);
|
||||
|
||||
std::unique_ptr<FileBuffer> buffer = std::make_unique<FileBufferAscii>(_params.filepath);
|
||||
|
||||
write_vertices(*buffer.get(), *plyData.get());
|
||||
|
||||
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 = blender::tests::flags_test_release_dir() + "/" + temp_file_path;
|
||||
PLYExportParams _params;
|
||||
_params.ascii_format = false;
|
||||
_params.export_normals = true;
|
||||
_params.export_colors = false;
|
||||
BLI_strncpy(_params.filepath, filePath.c_str(), 1024);
|
||||
|
||||
std::unique_ptr<PlyData> plyData = load_cube(_params);
|
||||
|
||||
std::unique_ptr<FileBuffer> buffer = std::make_unique<FileBufferBinary>(_params.filepath);
|
||||
|
||||
write_vertices(*buffer.get(), *plyData.get());
|
||||
|
||||
buffer->close_file();
|
||||
|
||||
std::vector<char> result = read_temp_file_in_vectorchar(filePath);
|
||||
|
||||
std::vector<char> expected(
|
||||
{(char)0x62, (char)0xA0, (char)0x8F, (char)0x3F, (char)0x62, (char)0xA0, (char)0x8F,
|
||||
(char)0x3F, (char)0x62, (char)0xA0, (char)0x8F, (char)0x3F, (char)0x3B, (char)0xCD,
|
||||
(char)0x13, (char)0xBF, (char)0x3B, (char)0xCD, (char)0x13, (char)0xBF, (char)0x3B,
|
||||
(char)0xCD, (char)0x13, (char)0xBF, (char)0x62, (char)0xA0, (char)0x8F, (char)0x3F,
|
||||
(char)0x62, (char)0xA0, (char)0x8F, (char)0x3F, (char)0x62, (char)0xA0, (char)0x8F,
|
||||
(char)0xBF, (char)0x3B, (char)0xCD, (char)0x13, (char)0xBF, (char)0x3B, (char)0xCD,
|
||||
(char)0x13, (char)0xBF, (char)0x3B, (char)0xCD, (char)0x13, (char)0x3F, (char)0x62,
|
||||
(char)0xA0, (char)0x8F, (char)0x3F, (char)0x62, (char)0xA0, (char)0x8F, (char)0xBF,
|
||||
(char)0x62, (char)0xA0, (char)0x8F, (char)0x3F, (char)0x3B, (char)0xCD, (char)0x13,
|
||||
(char)0xBF, (char)0x3B, (char)0xCD, (char)0x13, (char)0x3F, (char)0x3B, (char)0xCD,
|
||||
(char)0x13, (char)0xBF, (char)0x62, (char)0xA0, (char)0x8F, (char)0x3F, (char)0x62,
|
||||
(char)0xA0, (char)0x8F, (char)0xBF, (char)0x62, (char)0xA0, (char)0x8F, (char)0xBF,
|
||||
(char)0x3B, (char)0xCD, (char)0x13, (char)0xBF, (char)0x3B, (char)0xCD, (char)0x13,
|
||||
(char)0x3F, (char)0x3B, (char)0xCD, (char)0x13, (char)0x3F, (char)0x62, (char)0xA0,
|
||||
(char)0x8F, (char)0xBF, (char)0x62, (char)0xA0, (char)0x8F, (char)0x3F, (char)0x62,
|
||||
(char)0xA0, (char)0x8F, (char)0x3F, (char)0x3B, (char)0xCD, (char)0x13, (char)0x3F,
|
||||
(char)0x3B, (char)0xCD, (char)0x13, (char)0xBF, (char)0x3B, (char)0xCD, (char)0x13,
|
||||
(char)0xBF, (char)0x62, (char)0xA0, (char)0x8F, (char)0xBF, (char)0x62, (char)0xA0,
|
||||
(char)0x8F, (char)0x3F, (char)0x62, (char)0xA0, (char)0x8F, (char)0xBF, (char)0x3B,
|
||||
(char)0xCD, (char)0x13, (char)0x3F, (char)0x3B, (char)0xCD, (char)0x13, (char)0xBF,
|
||||
(char)0x3B, (char)0xCD, (char)0x13, (char)0x3F, (char)0x62, (char)0xA0, (char)0x8F,
|
||||
(char)0xBF, (char)0x62, (char)0xA0, (char)0x8F, (char)0xBF, (char)0x62, (char)0xA0,
|
||||
(char)0x8F, (char)0x3F, (char)0x3B, (char)0xCD, (char)0x13, (char)0x3F, (char)0x3B,
|
||||
(char)0xCD, (char)0x13, (char)0x3F, (char)0x3B, (char)0xCD, (char)0x13, (char)0xBF,
|
||||
(char)0x62, (char)0xA0, (char)0x8F, (char)0xBF, (char)0x62, (char)0xA0, (char)0x8F,
|
||||
(char)0xBF, (char)0x62, (char)0xA0, (char)0x8F, (char)0xBF, (char)0x3B, (char)0xCD,
|
||||
(char)0x13, (char)0x3F, (char)0x3B, (char)0xCD, (char)0x13, (char)0x3F, (char)0x3B,
|
||||
(char)0xCD, (char)0x13, (char)0x3F});
|
||||
|
||||
ASSERT_EQ(result.size(), expected.size());
|
||||
|
||||
for (int i = 0; i < result.size(); i++) {
|
||||
ASSERT_EQ(result[i], expected[i]);
|
||||
}
|
||||
}
|
||||
|
||||
class ply_exporter_ply_data_test : 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(ply_exporter_ply_data_test, CubeLoadPLYDataVertices)
|
||||
{
|
||||
PLYExportParams params;
|
||||
PlyData plyData = load_ply_data_from_blendfile("io_tests/blend_geometry/cube_all_data.blend",
|
||||
params);
|
||||
EXPECT_EQ(plyData.vertices.size(), 8);
|
||||
}
|
||||
TEST_F(ply_exporter_ply_data_test, 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.UV_coordinates.size(), 8);
|
||||
}
|
||||
TEST_F(ply_exporter_ply_data_test, 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);
|
||||
}
|
||||
|
||||
TEST_F(ply_exporter_ply_data_test, CubeLoadPLYDataUVDisabled)
|
||||
{
|
||||
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.UV_coordinates.size(), 0);
|
||||
}
|
||||
|
||||
} // namespace blender::io::ply
|
|
@ -0,0 +1,248 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "tests/blendfile_loading_base_test.h"
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_mesh.h"
|
||||
#include "BKE_object.h"
|
||||
|
||||
#include "BLO_readfile.h"
|
||||
|
||||
#include "DEG_depsgraph_query.h"
|
||||
|
||||
#include "IO_ply.h"
|
||||
#include "ply_data.hh"
|
||||
#include "ply_import.hh"
|
||||
#include "ply_import_binary.hh"
|
||||
|
||||
namespace blender::io::ply {
|
||||
|
||||
struct Expectation {
|
||||
std::string name;
|
||||
PlyFormatType type;
|
||||
int totvert, totpoly, totedge;
|
||||
float3 vert_first, vert_last;
|
||||
float3 normal_first = {0, 0, 0};
|
||||
float2 uv_first;
|
||||
float4 color_first = {-1, -1, -1, -1};
|
||||
};
|
||||
|
||||
class PlyImportTest : public BlendfileLoadingBaseTest {
|
||||
public:
|
||||
void import_and_check(const char *path, const Expectation *expect, size_t expect_count)
|
||||
{
|
||||
if (!blendfile_load("io_tests/blend_geometry/all_quads.blend")) {
|
||||
ADD_FAILURE();
|
||||
return;
|
||||
}
|
||||
|
||||
PLYImportParams params;
|
||||
params.global_scale = 1.0f;
|
||||
params.forward_axis = IO_AXIS_NEGATIVE_Z;
|
||||
params.up_axis = IO_AXIS_Y;
|
||||
params.merge_verts = false;
|
||||
|
||||
/* Import the test file. */
|
||||
std::string ply_path = blender::tests::flags_test_asset_dir() + "/io_tests/ply/" + path;
|
||||
strncpy(params.filepath, ply_path.c_str(), FILE_MAX - 1);
|
||||
importer_main(bfile->main, bfile->curscene, bfile->cur_view_layer, params, nullptr);
|
||||
|
||||
depsgraph_create(DAG_EVAL_VIEWPORT);
|
||||
|
||||
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;
|
||||
size_t object_index = 0;
|
||||
|
||||
/* Iterate over the objects in the viewport */
|
||||
DEG_OBJECT_ITER_BEGIN (°_iter_settings, object) {
|
||||
if (object_index >= expect_count) {
|
||||
ADD_FAILURE();
|
||||
break;
|
||||
}
|
||||
|
||||
const Expectation &exp = expect[object_index];
|
||||
|
||||
ASSERT_STREQ(object->id.name, exp.name.c_str());
|
||||
EXPECT_V3_NEAR(object->loc, float3(0, 0, 0), 0.0001f);
|
||||
|
||||
EXPECT_V3_NEAR(object->scale, float3(1, 1, 1), 0.0001f);
|
||||
if (object->type == OB_MESH) {
|
||||
Mesh *mesh = BKE_object_get_evaluated_mesh(object);
|
||||
|
||||
/* Test if mesh has expected amount of vertices, edges, and faces. */
|
||||
ASSERT_EQ(mesh->totvert, exp.totvert);
|
||||
ASSERT_EQ(mesh->totedge, exp.totedge);
|
||||
ASSERT_EQ(mesh->totpoly, exp.totpoly);
|
||||
|
||||
/* Test if first and last vertices match. */
|
||||
const Span<float3> verts = mesh->vert_positions();
|
||||
EXPECT_V3_NEAR(verts.first(), exp.vert_first, 0.0001f);
|
||||
EXPECT_V3_NEAR(verts.last(), exp.vert_last, 0.0001f);
|
||||
|
||||
/* Fetch normal data from mesh and test if it matches expectation. */
|
||||
if (BKE_mesh_has_custom_loop_normals(mesh)) {
|
||||
const Span<float3> vertex_normals = mesh->vert_normals();
|
||||
ASSERT_FALSE(vertex_normals.is_empty());
|
||||
EXPECT_V3_NEAR(vertex_normals[0], exp.normal_first, 0.0001f);
|
||||
}
|
||||
|
||||
/* Fetch UV data from mesh and test if it matches expectation. */
|
||||
blender::bke::AttributeAccessor attributes = mesh->attributes();
|
||||
VArray<float2> uvs = attributes.lookup<float2>("UVMap");
|
||||
float2 uv_first = !uvs.is_empty() ? uvs[0] : float2(0, 0);
|
||||
EXPECT_V2_NEAR(uv_first, exp.uv_first, 0.0001f);
|
||||
|
||||
/* Check if expected mesh has vertex colors, and tests if it matches. */
|
||||
if (CustomData_has_layer(&mesh->vdata, CD_PROP_COLOR)) {
|
||||
const float4 *colors = (const float4 *)CustomData_get_layer(&mesh->vdata, CD_PROP_COLOR);
|
||||
ASSERT_TRUE(colors != nullptr);
|
||||
EXPECT_V4_NEAR(colors[0], exp.color_first, 0.0001f);
|
||||
}
|
||||
}
|
||||
++object_index;
|
||||
}
|
||||
|
||||
DEG_OBJECT_ITER_END;
|
||||
EXPECT_EQ(object_index, expect_count);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(PlyImportTest, PLYImportCube)
|
||||
{
|
||||
Expectation expect[] = {{"OBCube",
|
||||
ASCII,
|
||||
8,
|
||||
6,
|
||||
12,
|
||||
float3(1, 1, -1),
|
||||
float3(-1, 1, 1),
|
||||
float3(0.5773, 0.5773, -0.5773),
|
||||
float2(0, 0)},
|
||||
{"OBcube_ascii",
|
||||
ASCII,
|
||||
24,
|
||||
6,
|
||||
24,
|
||||
float3(1, 1, -1),
|
||||
float3(-1, 1, 1),
|
||||
float3(0, 0, -1),
|
||||
float2(0.979336, 0.844958),
|
||||
float4(1, 0.8470, 0, 1)}};
|
||||
import_and_check("cube_ascii.ply", expect, 2);
|
||||
}
|
||||
|
||||
TEST_F(PlyImportTest, PLYImportASCIIEdgeTest)
|
||||
{
|
||||
Expectation expect[] = {{"OBCube",
|
||||
ASCII,
|
||||
8,
|
||||
6,
|
||||
12,
|
||||
float3(1, 1, -1),
|
||||
float3(-1, 1, 1),
|
||||
float3(0.5773, 0.5773, -0.5773)},
|
||||
{"OBASCII_wireframe_cube",
|
||||
ASCII,
|
||||
8,
|
||||
0,
|
||||
12,
|
||||
float3(-1, -1, -1),
|
||||
float3(1, 1, 1),
|
||||
float3(-2, 0, -1)}};
|
||||
|
||||
import_and_check("ASCII_wireframe_cube.ply", expect, 2);
|
||||
}
|
||||
|
||||
TEST_F(PlyImportTest, PLYImportBunny)
|
||||
{
|
||||
Expectation expect[] = {{"OBCube",
|
||||
ASCII,
|
||||
8,
|
||||
6,
|
||||
12,
|
||||
float3(1, 1, -1),
|
||||
float3(-1, 1, 1),
|
||||
float3(0.5773, 0.5773, -0.5773)},
|
||||
{"OBbunny2",
|
||||
BINARY_LE,
|
||||
1623,
|
||||
1000,
|
||||
1513,
|
||||
float3(0.0380425, 0.109755, 0.0161689),
|
||||
float3(-0.0722821, 0.143895, -0.0129091),
|
||||
float3(-2, -2, -2)}};
|
||||
import_and_check("bunny2.ply", expect, 2);
|
||||
}
|
||||
|
||||
TEST_F(PlyImportTest, PlyImportManySmallHoles)
|
||||
{
|
||||
Expectation expect[] = {{"OBCube",
|
||||
ASCII,
|
||||
8,
|
||||
6,
|
||||
12,
|
||||
float3(1, 1, -1),
|
||||
float3(-1, 1, 1),
|
||||
float3(0.5773, 0.5773, -0.5773)},
|
||||
{"OBmany_small_holes",
|
||||
BINARY_LE,
|
||||
2004,
|
||||
3524,
|
||||
5564,
|
||||
float3(-0.0131592, -0.0598382, 1.58958),
|
||||
float3(-0.0177622, 0.0105153, 1.61977),
|
||||
float3(-2, -2, -2),
|
||||
float2(0, 0),
|
||||
float4(0.7215, 0.6784, 0.6627, 1)}};
|
||||
import_and_check("many_small_holes.ply", expect, 2);
|
||||
}
|
||||
|
||||
TEST_F(PlyImportTest, PlyImportWireframeCube)
|
||||
{
|
||||
Expectation expect[] = {{"OBCube",
|
||||
ASCII,
|
||||
8,
|
||||
6,
|
||||
12,
|
||||
float3(1, 1, -1),
|
||||
float3(-1, 1, 1),
|
||||
float3(0.5773, 0.5773, -0.5773)},
|
||||
{"OBwireframe_cube",
|
||||
BINARY_LE,
|
||||
8,
|
||||
0,
|
||||
12,
|
||||
float3(-1, -1, -1),
|
||||
float3(1, 1, 1),
|
||||
float3(-2, -2, -2)}};
|
||||
import_and_check("wireframe_cube.ply", expect, 2);
|
||||
}
|
||||
|
||||
TEST(PlyImportFunctionsTest, PlySwapBytes)
|
||||
{
|
||||
/* Individual bits shouldn't swap with each other. */
|
||||
uint8_t val8 = 0xA8;
|
||||
uint8_t exp8 = 0xA8;
|
||||
uint8_t actual8 = swap_bytes<uint8_t>(val8);
|
||||
ASSERT_EQ(exp8, actual8);
|
||||
|
||||
uint16_t val16 = 0xFEB0;
|
||||
uint16_t exp16 = 0xB0FE;
|
||||
uint16_t actual16 = swap_bytes<uint16_t>(val16);
|
||||
ASSERT_EQ(exp16, actual16);
|
||||
|
||||
uint32_t val32 = 0x80A37B0A;
|
||||
uint32_t exp32 = 0x0A7BA380;
|
||||
uint32_t actual32 = swap_bytes<uint32_t>(val32);
|
||||
ASSERT_EQ(exp32, actual32);
|
||||
|
||||
uint64_t val64 = 0x0102030405060708;
|
||||
uint64_t exp64 = 0x0807060504030201;
|
||||
uint64_t actual64 = swap_bytes<uint64_t>(val64);
|
||||
ASSERT_EQ(exp64, actual64);
|
||||
}
|
||||
|
||||
} // namespace blender::io::ply
|
|
@ -311,6 +311,10 @@ if(WITH_IO_WAVEFRONT_OBJ)
|
|||
add_definitions(-DWITH_IO_WAVEFRONT_OBJ)
|
||||
endif()
|
||||
|
||||
if(WITH_IO_PLY)
|
||||
add_definitions(-DWITH_IO_PLY)
|
||||
endif()
|
||||
|
||||
if(WITH_IO_STL)
|
||||
add_definitions(-DWITH_IO_STL)
|
||||
endif()
|
||||
|
|
|
@ -44,6 +44,7 @@ static PyStructSequence_Field app_builtopts_info_fields[] = {
|
|||
{"mod_remesh", NULL},
|
||||
{"collada", NULL},
|
||||
{"io_wavefront_obj", NULL},
|
||||
{"io_ply",NULL},
|
||||
{"io_stl", NULL},
|
||||
{"io_gpencil", NULL},
|
||||
{"opencolorio", NULL},
|
||||
|
@ -260,6 +261,12 @@ static PyObject *make_builtopts_info(void)
|
|||
SetObjIncref(Py_False);
|
||||
#endif
|
||||
|
||||
#ifdef WITH_IO_PLY
|
||||
SetObjIncref(Py_True);
|
||||
#else
|
||||
SetObjIncref(Py_False);
|
||||
#endif
|
||||
|
||||
#ifdef WITH_IO_STL
|
||||
SetObjIncref(Py_True);
|
||||
#else
|
||||
|
|
Loading…
Reference in New Issue