WIP: IO: C++ STL exporter #105598
|
@ -508,6 +508,8 @@ class TOPBAR_MT_file_export(Menu):
|
||||||
self.layout.operator("wm.obj_export", text="Wavefront (.obj)")
|
self.layout.operator("wm.obj_export", text="Wavefront (.obj)")
|
||||||
if bpy.app.build_options.io_ply:
|
if bpy.app.build_options.io_ply:
|
||||||
self.layout.operator("wm.ply_export", text="Stanford PLY (.ply) (experimental)")
|
self.layout.operator("wm.ply_export", text="Stanford PLY (.ply) (experimental)")
|
||||||
|
if bpy.app.build_options.io_stl:
|
||||||
|
self.layout.operator("wm.stl_export", text="STL (.stl) (experimental)")
|
||||||
|
|
||||||
|
|
||||||
class TOPBAR_MT_file_external_data(Menu):
|
class TOPBAR_MT_file_external_data(Menu):
|
||||||
|
|
|
@ -72,5 +72,6 @@ void ED_operatortypes_io(void)
|
||||||
|
|
||||||
#ifdef WITH_IO_STL
|
#ifdef WITH_IO_STL
|
||||||
WM_operatortype_append(WM_OT_stl_import);
|
WM_operatortype_append(WM_OT_stl_import);
|
||||||
|
WM_operatortype_append(WM_OT_stl_export);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,14 +14,162 @@
|
||||||
|
|
||||||
# include "DNA_space_types.h"
|
# include "DNA_space_types.h"
|
||||||
|
|||||||
|
|
||||||
|
# include "ED_fileselect.h"
|
||||||
# include "ED_outliner.h"
|
# include "ED_outliner.h"
|
||||||
|
|
||||||
# include "RNA_access.h"
|
# include "RNA_access.h"
|
||||||
# include "RNA_define.h"
|
# include "RNA_define.h"
|
||||||
|
|
||||||
|
# include "BLT_translation.h"
|
||||||
|
|
||||||
|
# include "UI_interface.h"
|
||||||
|
# include "UI_resources.h"
|
||||||
|
|
||||||
# include "IO_stl.h"
|
# include "IO_stl.h"
|
||||||
# include "io_stl_ops.h"
|
# include "io_stl_ops.h"
|
||||||
|
|
||||||
|
static int wm_stl_export_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
|
||||||
|
{
|
||||||
|
ED_fileselect_ensure_default_filepath(C, op, ".stl");
|
||||||
|
|
||||||
|
WM_event_add_fileselect(C, op);
|
||||||
|
return OPERATOR_RUNNING_MODAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int wm_stl_export_execute(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 STLExportParams export_params;
|
||||||
|
RNA_string_get(op->ptr, "filepath", export_params.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.use_apply_modifiers = RNA_boolean_get(op->ptr, "use_apply_modifiers");
|
||||||
EyadAhmed marked this conversation as resolved
Outdated
Aras Pranckevicius
commented
Maybe instead of Maybe instead of `use_apply_modifiers` this could be `apply_modifiers`? That way it would be consistent with OBJ, PLY, Collada APIs.
|
|||||||
|
export_params.use_selection_only = RNA_boolean_get(op->ptr, "use_selection_only");
|
||||||
EyadAhmed marked this conversation as resolved
Outdated
Aras Pranckevicius
commented
Similar, maybe instead of Similar, maybe instead of `use_selection_only` it should follow some existing export APIs. The trouble is... there's no consistency for that name. Alembic and Collada use `selected`, USD uses `selected_objects_only`, OBJ and PLY use `export_selected_objects`. But at least picking one of those is better than introducing yet another new name :)
|
|||||||
|
export_params.use_ascii = RNA_boolean_get(op->ptr, "use_ascii");
|
||||||
EyadAhmed marked this conversation as resolved
Outdated
Aras Pranckevicius
commented
PLY export uses PLY export uses `ascii_format` name for the same argument, so maybe follow that.
|
|||||||
|
export_params.use_batch = RNA_boolean_get(op->ptr, "use_batch");
|
||||||
|
|
||||||
|
STL_export(C, &export_params);
|
||||||
|
|
||||||
|
return OPERATOR_FINISHED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ui_stl_export_settings(uiLayout *layout, PointerRNA *op_props_ptr)
|
||||||
|
{
|
||||||
|
uiLayoutSetPropSep(layout, true);
|
||||||
|
uiLayoutSetPropDecorate(layout, false);
|
||||||
|
|
||||||
|
uiLayout *box, *col, *sub;
|
||||||
|
|
||||||
|
box = uiLayoutBox(layout);
|
||||||
|
col = uiLayoutColumn(box, false);
|
||||||
|
uiItemR(col, op_props_ptr, "use_ascii", 0, IFACE_("ASCII"), ICON_NONE);
|
||||||
|
uiItemR(col, op_props_ptr, "use_batch", 0, IFACE_("Batch"), ICON_NONE);
|
||||||
|
|
||||||
|
box = uiLayoutBox(layout);
|
||||||
|
sub = uiLayoutColumnWithHeading(box, false, IFACE_("Include"));
|
||||||
|
uiItemR(sub, op_props_ptr, "use_selection_only", 0, IFACE_("Selection Only"), ICON_NONE);
|
||||||
|
|
||||||
|
box = uiLayoutBox(layout);
|
||||||
|
sub = uiLayoutColumnWithHeading(box, false, IFACE_("Transform"));
|
||||||
|
uiItemR(sub, op_props_ptr, "global_scale", 0, IFACE_("Scale"), ICON_NONE);
|
||||||
|
uiItemR(sub, op_props_ptr, "use_scene_unit", 0, IFACE_("Scene Unit"), ICON_NONE);
|
||||||
|
uiItemR(sub, op_props_ptr, "forward_axis", 0, IFACE_("Forward"), ICON_NONE);
|
||||||
|
uiItemR(sub, op_props_ptr, "up_axis", 0, IFACE_("Up"), ICON_NONE);
|
||||||
|
|
||||||
|
box = uiLayoutBox(layout);
|
||||||
|
sub = uiLayoutColumnWithHeading(box, false, IFACE_("Geometry"));
|
||||||
|
uiItemR(sub, op_props_ptr, "use_apply_modifiers", 0, IFACE_("Apply Modifiers"), ICON_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void wm_stl_export_draw(bContext *UNUSED(C), wmOperator *op)
|
||||||
|
{
|
||||||
|
PointerRNA ptr;
|
||||||
|
RNA_pointer_create(NULL, op->type->srna, op->properties, &ptr);
|
||||||
|
ui_stl_export_settings(op->layout, &ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if any property in the UI is changed.
|
||||||
|
*/
|
||||||
|
static bool wm_stl_export_check(bContext *UNUSED(C), wmOperator *op)
|
||||||
|
{
|
||||||
|
char filepath[FILE_MAX];
|
||||||
|
bool changed = false;
|
||||||
|
RNA_string_get(op->ptr, "filepath", filepath);
|
||||||
|
|
||||||
|
if (!BLI_path_extension_check(filepath, ".stl")) {
|
||||||
|
BLI_path_extension_ensure(filepath, FILE_MAX, ".stl");
|
||||||
|
RNA_string_set(op->ptr, "filepath", filepath);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WM_OT_stl_export(struct wmOperatorType *ot)
|
||||||
|
{
|
||||||
|
PropertyRNA *prop;
|
||||||
|
|
||||||
|
ot->name = "Export STL";
|
||||||
|
ot->description = "Save the scene to an STL file";
|
||||||
|
ot->idname = "WM_OT_stl_export";
|
||||||
|
|
||||||
|
ot->invoke = wm_stl_export_invoke;
|
||||||
|
ot->exec = wm_stl_export_execute;
|
||||||
|
ot->poll = WM_operator_winactive;
|
||||||
|
ot->ui = wm_stl_export_draw;
|
||||||
|
ot->check = wm_stl_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);
|
||||||
|
|
||||||
|
RNA_def_boolean(ot->srna,
|
||||||
|
"use_ascii",
|
||||||
|
false,
|
||||||
|
"ASCII Format",
|
||||||
|
"Export file in ASCII format, export as binary otherwise");
|
||||||
|
RNA_def_boolean(
|
||||||
|
ot->srna, "use_batch", false, "Batch Export", "Export each object to a separate file");
|
||||||
|
RNA_def_boolean(ot->srna,
|
||||||
|
"use_selection_only",
|
||||||
|
false,
|
||||||
|
"Export Selected Objects",
|
||||||
|
"Export only selected objects instead of all supported objects");
|
||||||
|
|
||||||
|
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 exported data");
|
||||||
|
|
||||||
|
prop = RNA_def_enum(ot->srna, "forward_axis", io_transform_axis, IO_AXIS_Y, "Forward Axis", "");
|
||||||
|
RNA_def_property_update_runtime(prop, (void *)io_ui_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 *)io_ui_up_axis_update);
|
||||||
|
|
||||||
|
RNA_def_boolean(ot->srna,
|
||||||
|
"use_apply_modifiers",
|
||||||
|
true,
|
||||||
|
"Apply Modifiers",
|
||||||
|
"Apply modifiers to exported meshes");
|
||||||
|
|
||||||
|
/* Only show .stl files by default. */
|
||||||
|
prop = RNA_def_string(ot->srna, "filter_glob", "*.stl", 0, "Extension Filter", "");
|
||||||
|
RNA_def_property_flag(prop, PROP_HIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
static int wm_stl_import_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
static int wm_stl_import_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
||||||
{
|
{
|
||||||
return WM_operator_filesel(C, op, event);
|
return WM_operator_filesel(C, op, event);
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
set(INC
|
set(INC
|
||||||
.
|
.
|
||||||
importer
|
importer
|
||||||
|
exporter
|
||||||
../common
|
../common
|
||||||
../../blenkernel
|
../../blenkernel
|
||||||
../../blenlib
|
../../blenlib
|
||||||
|
@ -15,6 +16,7 @@ set(INC
|
||||||
../../nodes
|
../../nodes
|
||||||
../../windowmanager
|
../../windowmanager
|
||||||
../../../../extern/fast_float
|
../../../../extern/fast_float
|
||||||
|
../../../../extern/fmtlib/include
|
||||||
../../../../intern/guardedalloc
|
../../../../intern/guardedalloc
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -28,12 +30,18 @@ set(SRC
|
||||||
importer/stl_import_ascii_reader.cc
|
importer/stl_import_ascii_reader.cc
|
||||||
importer/stl_import_binary_reader.cc
|
importer/stl_import_binary_reader.cc
|
||||||
importer/stl_import_mesh.cc
|
importer/stl_import_mesh.cc
|
||||||
|
exporter/stl_export.cc
|
||||||
|
exporter/stl_export_writer.cc
|
||||||
|
|
||||||
IO_stl.h
|
IO_stl.h
|
||||||
importer/stl_import.hh
|
importer/stl_import.hh
|
||||||
importer/stl_import_ascii_reader.hh
|
importer/stl_import_ascii_reader.hh
|
||||||
importer/stl_import_binary_reader.hh
|
importer/stl_import_binary_reader.hh
|
||||||
importer/stl_import_mesh.hh
|
importer/stl_import_mesh.hh
|
||||||
|
exporter/stl_export_binary_writer.hh
|
||||||
|
exporter/stl_export_ascii_writer.hh
|
||||||
|
exporter/stl_export_writer.hh
|
||||||
|
exporter/stl_export.hh
|
||||||
)
|
)
|
||||||
|
|
||||||
set(LIB
|
set(LIB
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include "BLI_timeit.hh"
|
#include "BLI_timeit.hh"
|
||||||
|
|
||||||
#include "IO_stl.h"
|
#include "IO_stl.h"
|
||||||
|
#include "stl_export.hh"
|
||||||
#include "stl_import.hh"
|
#include "stl_import.hh"
|
||||||
|
|
||||||
void STL_import(bContext *C, const struct STLImportParams *import_params)
|
void STL_import(bContext *C, const struct STLImportParams *import_params)
|
||||||
|
@ -14,3 +15,9 @@ void STL_import(bContext *C, const struct STLImportParams *import_params)
|
||||||
SCOPED_TIMER("STL Import");
|
SCOPED_TIMER("STL Import");
|
||||||
blender::io::stl::importer_main(C, *import_params);
|
blender::io::stl::importer_main(C, *import_params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void STL_export(bContext *C, const struct STLExportParams *export_params)
|
||||||
|
{
|
||||||
|
SCOPED_TIMER("STL Export");
|
||||||
|
blender::io::stl::exporter_main(C, *export_params);
|
||||||
|
}
|
||||||
|
|
|
@ -25,11 +25,29 @@ struct STLImportParams {
|
||||||
bool use_mesh_validate;
|
bool use_mesh_validate;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct STLExportParams {
|
||||||
|
/** Full path to the to-be-saved STL file. */
|
||||||
|
char filepath[FILE_MAX];
|
||||||
|
eIOAxis forward_axis;
|
||||||
|
eIOAxis up_axis;
|
||||||
|
bool use_selection_only;
|
||||||
|
bool use_scene_unit;
|
||||||
|
bool use_apply_modifiers;
|
||||||
|
bool use_ascii;
|
||||||
|
bool use_batch;
|
||||||
|
float global_scale;
|
||||||
EyadAhmed marked this conversation as resolved
Outdated
Aras Pranckevicius
commented
Suuuper minor: I'd put the float member before the bool ones. Right now it wastes 3 bytes of padding between the last bool and the float, in order to align the float to 4-byte boundary. This does not really matter all that much for this struct, but as a general guidance it's a good one to learn. Suuuper minor: I'd put the float member before the bool ones. Right now it wastes 3 bytes of padding between the last bool and the float, in order to align the float to 4-byte boundary. This does not *really* matter all that much for this struct, but as a general guidance it's a good one to learn.
|
|||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* C-interface for the importer.
|
* C-interface for the importer.
|
||||||
*/
|
*/
|
||||||
void STL_import(bContext *C, const struct STLImportParams *import_params);
|
void STL_import(bContext *C, const struct STLImportParams *import_params);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* C-interface for the exporter.
|
||||||
|
*/
|
||||||
|
void STL_export(bContext *C, const struct STLExportParams *export_params);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
|
||||||
|
/** \file
|
||||||
|
* \ingroup stl
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "BKE_mesh.hh"
|
||||||
|
#include "BKE_object.h"
|
||||||
|
|
||||||
|
#include "DEG_depsgraph_query.h"
|
||||||
|
|
||||||
|
#include "DNA_layer_types.h"
|
||||||
|
#include "DNA_scene_types.h"
|
||||||
|
|
||||||
|
#include "BLI_math_vector.h"
|
||||||
|
#include "BLI_math_vector_types.hh"
|
||||||
|
|
||||||
|
#include "IO_stl.h"
|
||||||
|
|
||||||
|
#include "bmesh.h"
|
||||||
|
#include "bmesh_tools.h"
|
||||||
|
|
||||||
|
#include "stl_export.hh"
|
||||||
|
#include "stl_export_writer.hh"
|
||||||
|
|
||||||
|
namespace blender::io::stl {
|
||||||
|
|
||||||
|
void exporter_main(bContext *C, const STLExportParams &export_params)
|
||||||
|
{
|
||||||
|
std::unique_ptr<FileWriter> writer;
|
||||||
|
|
||||||
|
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
|
||||||
|
Scene *scene = CTX_data_scene(C);
|
||||||
|
|
||||||
|
/* If not exporting in batch, create single writer for all objects. */
|
||||||
|
if (!export_params.use_batch) {
|
||||||
|
writer = create_writer(export_params.filepath,
|
||||||
|
export_params.use_ascii ? FileWriter::Type::ASCII :
|
||||||
|
FileWriter::Type::BINARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
DEG_OBJECT_ITER_BEGIN (°_iter_settings, object) {
|
||||||
|
if (object->type != OB_MESH) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (export_params.use_selection_only && !(object->base_flag & BASE_SELECTED)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If exporting in batch, create writer for each iteration over objects. */
|
||||||
|
if (export_params.use_batch) {
|
||||||
|
/* Get object name by skipping initial "OB" prefix. */
|
||||||
|
std::string object_name = (object->id.name + 2);
|
||||||
|
/* Replace spaces with underscores. */
|
||||||
|
std::replace(object_name.begin(), object_name.end(), ' ', '_');
|
||||||
|
|
||||||
|
/* Include object name in the exported file name. */
|
||||||
|
std::string suffix = object_name + ".stl";
|
||||||
|
char filepath[FILE_MAX];
|
||||||
|
BLI_strncpy(filepath, export_params.filepath, FILE_MAX);
|
||||||
|
BLI_path_extension_replace(filepath, FILE_MAX, suffix.c_str());
|
||||||
|
writer = create_writer(
|
||||||
|
filepath, export_params.use_ascii ? FileWriter::Type::ASCII : FileWriter::Type::BINARY);
|
||||||
|
}
|
||||||
|
|
||||||
EyadAhmed marked this conversation as resolved
Outdated
Hans Goudey
commented
What do you think about using the What do you think about using the `Mesh` triangulation instead of converting to BMesh and triangulating there? That can be accessed with `Mesh.looptris()`
Eyad Ahmed
commented
Oh awesome, I will use it then Oh awesome, I will use it then
|
|||||||
|
Object *obj_eval = DEG_get_evaluated_object(depsgraph, object);
|
||||||
|
Object export_object_eval_ = dna::shallow_copy(*obj_eval);
|
||||||
|
Mesh *mesh = export_params.use_apply_modifiers ?
|
||||||
|
BKE_object_get_evaluated_mesh(&export_object_eval_) :
|
||||||
|
BKE_object_get_pre_modified_mesh(&export_object_eval_);
|
||||||
|
|
||||||
|
/* Calculate transform. */
|
||||||
|
float global_scale = export_params.global_scale;
|
||||||
|
if ((scene->unit.system != USER_UNIT_NONE) && export_params.use_scene_unit) {
|
||||||
|
global_scale *= scene->unit.scale_length;
|
||||||
|
}
|
||||||
|
float scale_vec[3] = {global_scale, global_scale, global_scale};
|
||||||
|
float obmat3x3[3][3];
|
||||||
Hans Goudey
commented
It would be best to try looking into using the new matrix types recently added in https://archive.blender.org/developer/D16625. Should be a nice cleanup here too! It would be best to try looking into using the new matrix types recently added in https://archive.blender.org/developer/D16625. Should be a nice cleanup here too!
|
|||||||
|
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, export_params.forward_axis, export_params.up_axis, obmat3x3);
|
||||||
|
copy_m4_m3(obmat4x4, obmat3x3);
|
||||||
|
rescale_m4(obmat4x4, scale_vec);
|
||||||
|
|
||||||
|
/* Write triangles. */
|
||||||
EyadAhmed marked this conversation as resolved
Outdated
Hans Goudey
commented
In similar Blender code, In similar Blender code, `auto` isn't generally used in this context. The type information gives helpful context to the reader. Same for `auto` in the loop below.
|
|||||||
|
const Span<float3> positions = mesh->vert_positions();
|
||||||
EyadAhmed marked this conversation as resolved
Outdated
Hans Goudey
commented
The typical name for this The typical name for this `vert_positions` span is `positions` rather than `vertices`. Vertices might refer to any/all vertex-domain attributes.
|
|||||||
|
const blender::Span<int> corner_verts = mesh->corner_verts();
|
||||||
|
for (const MLoopTri &loop_tri : mesh->looptris()) {
|
||||||
|
Triangle t{};
|
||||||
EyadAhmed marked this conversation as resolved
Outdated
Hans Goudey
commented
I expect that these I expect that these `vert_positions()` calls would show up in a profile. Each one does a string lookup in all vertex custom data layers. It would be better to retrieve the span once below.
|
|||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
float3 co = positions[corner_verts[loop_tri.tri[i]]];
|
||||||
|
mul_m4_v3(obmat4x4, co);
|
||||||
|
for (int j = 0; j < 3; j++) {
|
||||||
|
t.vertices[i][j] = co[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writer->write_triangle(&t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DEG_OBJECT_ITER_END;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace blender::io::stl
|
|
@ -0,0 +1,16 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
|
||||||
|
/** \file
|
||||||
|
* \ingroup stl
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "IO_stl.h"
|
||||||
|
|
||||||
|
namespace blender::io::stl {
|
||||||
|
|
||||||
|
/* Main export function used from within Blender. */
|
||||||
|
void exporter_main(bContext *C, const STLExportParams &export_params);
|
||||||
|
|
||||||
|
} // namespace blender::io::stl
|
|
@ -0,0 +1,67 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
|
||||||
|
/** \file
|
||||||
|
* \ingroup stl
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
#include "BLI_utility_mixins.hh"
|
||||||
|
|
||||||
|
/* SEP macro from BLI path utils clashes with SEP symbol in fmt headers. */
|
||||||
|
#undef SEP
|
||||||
|
#define FMT_HEADER_ONLY
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#include "stl_export_writer.hh"
|
||||||
|
|
||||||
|
namespace blender::io::stl {
|
||||||
|
|
||||||
|
class ASCIIFileWriter : public FileWriter, NonCopyable {
|
||||||
|
private:
|
||||||
|
std::ofstream file_;
|
||||||
Aras Pranckevicius
commented
`std::ofstream` will fail writing a file on Windows if path contains non-ASCII characters. Use `blender::fstream` from `BLI_fileops.hh` instead.
|
|||||||
|
|
||||||
|
public:
|
||||||
|
explicit ASCIIFileWriter(const char *filepath);
|
||||||
|
~ASCIIFileWriter() override;
|
||||||
|
void write_triangle(const Triangle *t) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
ASCIIFileWriter::ASCIIFileWriter(const char *filepath) : file_(filepath)
|
||||||
|
{
|
||||||
|
file_ << "solid \n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void ASCIIFileWriter::write_triangle(const Triangle *t)
|
||||||
|
{
|
||||||
|
file_ << fmt::format(
|
||||||
|
"facet normal {} {} {}\n"
|
||||||
|
"\touter loop\n"
|
||||||
|
"\t\tvertex {} {} {}\n"
|
||||||
|
"\t\tvertex {} {} {}\n"
|
||||||
|
"\t\tvertex {} {} {}\n"
|
||||||
|
"\tendloop\n"
|
||||||
|
"endfacet\n",
|
||||||
|
|
||||||
|
t->normal[0],
|
||||||
|
t->normal[1],
|
||||||
|
t->normal[2],
|
||||||
|
t->vertices[0][0],
|
||||||
|
t->vertices[0][1],
|
||||||
|
t->vertices[0][2],
|
||||||
|
t->vertices[1][0],
|
||||||
|
t->vertices[1][1],
|
||||||
|
t->vertices[1][2],
|
||||||
|
t->vertices[2][0],
|
||||||
|
t->vertices[2][1],
|
||||||
|
t->vertices[2][2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
ASCIIFileWriter::~ASCIIFileWriter()
|
||||||
|
{
|
||||||
|
file_ << "endsolid \n";
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace blender::io::stl
|
|
@ -0,0 +1,77 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
|
||||||
|
/** \file
|
||||||
|
* \ingroup stl
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include "BLI_assert.h"
|
||||||
|
#include "BLI_utility_mixins.hh"
|
||||||
|
|
||||||
|
#include "stl_export_writer.hh"
|
||||||
|
|
||||||
|
namespace blender::io::stl {
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct STLBinaryTriangle {
|
||||||
|
float normal[3]{};
|
||||||
Hans Goudey
commented
Use the Use the `float3` type here instead
|
|||||||
|
float vertices[3][3]{};
|
||||||
|
uint16_t attribute_byte_count{};
|
||||||
Hans Goudey
commented
The defaults with The defaults with `{}` shouldn't be necessary here
|
|||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
BLI_STATIC_ASSERT_ALIGN(STLBinaryTriangle,
|
||||||
|
sizeof(float[3]) + sizeof(float[3][3]) + sizeof(uint16_t));
|
||||||
|
|
||||||
|
class BinaryFileWriter : public FileWriter, NonCopyable {
|
||||||
|
private:
|
||||||
|
FILE *file_ = nullptr;
|
||||||
|
uint32_t tris_num_ = 0;
|
||||||
|
static constexpr size_t BINARY_HEADER_SIZE = 80;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit BinaryFileWriter(const char *filepath);
|
||||||
|
~BinaryFileWriter() override;
|
||||||
|
void write_triangle(const Triangle *t) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
BinaryFileWriter::BinaryFileWriter(const char *filepath)
|
||||||
|
{
|
||||||
|
file_ = fopen(filepath, "wb");
|
||||||
Aras Pranckevicius
commented
Similar to ASCII writer: Similar to ASCII writer: `fopen` will fail correctly writing to a non-ASCII path on Windows. Use `BLI_fopen` from `BLI_fileops.h` instead.
|
|||||||
|
if (file_ == nullptr) {
|
||||||
|
throw std::runtime_error("Failed to open file");
|
||||||
|
}
|
||||||
|
|
||||||
|
char header[BINARY_HEADER_SIZE] = {};
|
||||||
|
fwrite(header, 1, BINARY_HEADER_SIZE, file_);
|
||||||
|
/* Write placeholder for number of triangles, so that it can be updated later (after all
|
||||||
|
* triangles have been written). */
|
||||||
|
fwrite(&tris_num_, sizeof(uint32_t), 1, file_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BinaryFileWriter::write_triangle(const Triangle *t)
|
||||||
|
{
|
||||||
|
STLBinaryTriangle packed_triangle{};
|
||||||
|
memcpy(packed_triangle.normal, t->normal, sizeof(float[3]));
|
||||||
|
memcpy(packed_triangle.vertices, t->vertices, sizeof(float[3][3]));
|
||||||
|
packed_triangle.attribute_byte_count = 0;
|
||||||
|
|
||||||
|
if (fwrite(&packed_triangle, sizeof(STLBinaryTriangle), 1, file_) == 1) {
|
||||||
|
tris_num_++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BinaryFileWriter::~BinaryFileWriter()
|
||||||
|
{
|
||||||
|
assert(file_ != nullptr);
|
||||||
|
fseek(file_, BINARY_HEADER_SIZE, SEEK_SET);
|
||||||
|
fwrite(&tris_num_, sizeof(uint32_t), 1, file_);
|
||||||
|
fclose(file_);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace blender::io::stl
|
|
@ -0,0 +1,29 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
|
||||||
|
/** \file
|
||||||
|
* \ingroup stl
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include "stl_export_ascii_writer.hh"
|
||||||
|
#include "stl_export_binary_writer.hh"
|
||||||
|
#include "stl_export_writer.hh"
|
||||||
|
|
||||||
|
namespace blender::io::stl {
|
||||||
|
|
||||||
|
std::unique_ptr<FileWriter> create_writer(const char *filepath, FileWriter::Type type)
|
||||||
|
{
|
||||||
|
if (type == FileWriter::Type::ASCII) {
|
||||||
|
return std::make_unique<ASCIIFileWriter>(filepath);
|
||||||
|
}
|
||||||
|
else if (type == FileWriter::Type::BINARY) {
|
||||||
Hans Goudey
commented
else after return is redundant and can be removed here else after return is redundant and can be removed here
|
|||||||
|
return std::make_unique<BinaryFileWriter>(filepath);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw std::runtime_error("Not implemented");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace blender::io::stl
|
|
@ -0,0 +1,29 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
|
||||||
|
/** \file
|
||||||
|
* \ingroup stl
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace blender::io::stl {
|
||||||
|
|
||||||
|
struct Triangle {
|
||||||
|
float normal[3]{};
|
||||||
|
float vertices[3][3]{};
|
||||||
|
};
|
||||||
|
|
||||||
|
class FileWriter {
|
||||||
|
public:
|
||||||
|
enum class Type { BINARY, ASCII };
|
||||||
|
|
||||||
|
virtual ~FileWriter() = default;
|
||||||
|
virtual void write_triangle(const Triangle *t) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<FileWriter> create_writer(const char *filepath, FileWriter::Type type);
|
||||||
|
|
||||||
|
} // namespace blender::io::stl
|
Best to add this new file as a C++ file, that's the direction Blender is moving in generally.