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)")
|
||||
if bpy.app.build_options.io_ply:
|
||||
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):
|
||||
|
|
|
@ -72,5 +72,6 @@ void ED_operatortypes_io(void)
|
|||
|
||||
#ifdef WITH_IO_STL
|
||||
WM_operatortype_append(WM_OT_stl_import);
|
||||
WM_operatortype_append(WM_OT_stl_export);
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -14,14 +14,162 @@
|
|||
|
||||
# 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 "UI_interface.h"
|
||||
# include "UI_resources.h"
|
||||
|
||||
# include "IO_stl.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)
|
||||
{
|
||||
return WM_operator_filesel(C, op, event);
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
set(INC
|
||||
.
|
||||
importer
|
||||
exporter
|
||||
../common
|
||||
../../blenkernel
|
||||
../../blenlib
|
||||
|
@ -15,6 +16,7 @@ set(INC
|
|||
../../nodes
|
||||
../../windowmanager
|
||||
../../../../extern/fast_float
|
||||
../../../../extern/fmtlib/include
|
||||
../../../../intern/guardedalloc
|
||||
)
|
||||
|
||||
|
@ -28,12 +30,18 @@ set(SRC
|
|||
importer/stl_import_ascii_reader.cc
|
||||
importer/stl_import_binary_reader.cc
|
||||
importer/stl_import_mesh.cc
|
||||
exporter/stl_export.cc
|
||||
exporter/stl_export_writer.cc
|
||||
|
||||
IO_stl.h
|
||||
importer/stl_import.hh
|
||||
importer/stl_import_ascii_reader.hh
|
||||
importer/stl_import_binary_reader.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
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "BLI_timeit.hh"
|
||||
|
||||
#include "IO_stl.h"
|
||||
#include "stl_export.hh"
|
||||
#include "stl_import.hh"
|
||||
|
||||
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");
|
||||
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;
|
||||
};
|
||||
|
||||
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.
|
||||
*/
|
||||
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
|
||||
}
|
||||
#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.