Blender V5.0
obj_exporter.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include <cstdio>
10#include <memory>
11#include <system_error>
12
13#include "DNA_curve_enums.h"
14#include "DNA_curve_types.h"
15
16#include "BKE_context.hh"
17#include "BKE_lib_id.hh"
18#include "BKE_report.hh"
19#include "BKE_scene.hh"
20
21#include "BLI_path_utils.hh"
22#include "BLI_string.h"
23#include "BLI_task.hh"
24#include "BLI_vector.hh"
25
27
29#include "DNA_scene_types.h"
30
31#include "ED_object.hh"
32
33#include "obj_export_mesh.hh"
34#include "obj_export_nurbs.hh"
35#include "obj_exporter.hh"
36
38
39#include "CLG_log.h"
40static CLG_LogRef LOG = {"io.obj"};
41
42namespace blender::io::obj {
43
45 const eEvaluationMode eval_mode,
46 Collection *collection)
47{
48 Scene *scene = CTX_data_scene(C);
49 Main *bmain = CTX_data_main(C);
50 ViewLayer *view_layer = CTX_data_view_layer(C);
51
52 /* If a collection was provided, use it. */
53 if (collection) {
54 depsgraph_ = DEG_graph_new(bmain, scene, view_layer, eval_mode);
55 needs_free_ = true;
56 DEG_graph_build_from_collection(depsgraph_, collection);
57 BKE_scene_graph_evaluated_ensure(depsgraph_, bmain);
58 }
59 else if (eval_mode == DAG_EVAL_RENDER) {
60 depsgraph_ = DEG_graph_new(bmain, scene, view_layer, eval_mode);
61 needs_free_ = true;
63 BKE_scene_graph_evaluated_ensure(depsgraph_, bmain);
64 }
65 else {
67 needs_free_ = false;
68 }
69}
70
72{
73 if (needs_free_) {
74 DEG_graph_free(depsgraph_);
75 }
76}
77
79{
80 return depsgraph_;
81}
82
87
88static void print_exception_error(const std::system_error &ex)
89{
90 CLOG_ERROR(&LOG, "[%s] %s", ex.code().category().name(), ex.what());
91}
92
93static bool is_curve_nurbs_compatible(const Nurb *nurb)
94{
95 while (nurb) {
96 if (nurb->type == CU_BEZIER || nurb->pntsv != 1) {
97 return false;
98 }
99 nurb = nurb->next;
100 }
101 return true;
102}
103
109std::pair<Vector<std::unique_ptr<OBJMesh>>, Vector<std::unique_ptr<IOBJCurve>>>
110filter_supported_objects(Depsgraph *depsgraph, const OBJExportParams &export_params)
111{
112 Vector<std::unique_ptr<OBJMesh>> r_exportable_meshes;
113 Vector<std::unique_ptr<IOBJCurve>> r_exportable_nurbs;
114 DEGObjectIterSettings deg_iter_settings{};
115 deg_iter_settings.depsgraph = depsgraph;
116 deg_iter_settings.flags = DEG_ITER_OBJECT_FLAG_LINKED_DIRECTLY |
119 DEG_OBJECT_ITER_BEGIN (&deg_iter_settings, object) {
120 if (export_params.export_selected_objects && !(object->base_flag & BASE_SELECTED)) {
121 continue;
122 }
123 switch (object->type) {
124 case OB_SURF:
125 /* Evaluated surface objects appear as mesh objects from the iterator. */
126 break;
127 case OB_MESH:
128 r_exportable_meshes.append(std::make_unique<OBJMesh>(depsgraph, export_params, object));
129 break;
130 case OB_CURVES_LEGACY: {
131 Curve *curve = static_cast<Curve *>(object->data);
132 Nurb *nurb{static_cast<Nurb *>(curve->nurb.first)};
133 if (!nurb) {
134 /* An empty curve. Not yet supported to export these as meshes. */
135 if (export_params.export_curves_as_nurbs) {
136 IOBJCurve *obj_curve = new OBJLegacyCurve(depsgraph, object);
137 r_exportable_nurbs.append(std::unique_ptr<IOBJCurve>(obj_curve));
138 }
139 break;
140 }
141 if (export_params.export_curves_as_nurbs && is_curve_nurbs_compatible(nurb)) {
142 /* Export in parameter form: control points. */
143 IOBJCurve *obj_curve = new OBJLegacyCurve(depsgraph, object);
144 r_exportable_nurbs.append(std::unique_ptr<IOBJCurve>(obj_curve));
145 }
146 else {
147 /* Export in mesh form: edges and vertices. */
148 r_exportable_meshes.append(std::make_unique<OBJMesh>(depsgraph, export_params, object));
149 }
150 break;
151 }
152 default:
153 /* Other object types are not supported. */
154 break;
155 }
156 }
158 return {std::move(r_exportable_meshes), std::move(r_exportable_nurbs)};
159}
160
161static void write_mesh_objects(const Span<std::unique_ptr<OBJMesh>> exportable_as_mesh,
162 OBJWriter &obj_writer,
163 MTLWriter *mtl_writer,
164 const OBJExportParams &export_params)
165{
166 /* Parallelization is over meshes/objects, which means
167 * we have to have the output text buffer for each object,
168 * and write them all into the file at the end. */
169 size_t count = exportable_as_mesh.size();
171
172 /* Serial: gather material indices, ensure normals & edges. */
173 Vector<Vector<int>> mtlindices;
174 if (mtl_writer) {
175 if (export_params.export_materials) {
176 obj_writer.write_mtllib_name(mtl_writer->mtl_file_path());
177 }
178 mtlindices.reserve(count);
179 }
180 for (const auto &obj_mesh : exportable_as_mesh) {
181 OBJMesh &obj = *obj_mesh;
182 if (mtl_writer) {
183 mtlindices.append(mtl_writer->add_materials(obj));
184 }
185 }
186
187 /* Parallel over meshes: store normal coords & indices, uv coords and indices. */
189 for (const int i : range) {
190 OBJMesh &obj = *exportable_as_mesh[i];
191 if (export_params.export_normals) {
192 obj.store_normal_coords_and_indices();
193 }
194 if (export_params.export_uv) {
195 obj.store_uv_coords_and_indices();
196 }
197 }
198 });
199
200 /* Serial: calculate index offsets; these are sequentially added
201 * over all meshes, and requite normal/uv indices to be calculated. */
202 Vector<IndexOffsets> index_offsets;
203 index_offsets.reserve(count);
204 IndexOffsets offsets{0, 0, 0};
205 for (const auto &obj_mesh : exportable_as_mesh) {
206 OBJMesh &obj = *obj_mesh;
207 index_offsets.append(offsets);
208 offsets.vertex_offset += obj.tot_vertices();
209 offsets.uv_vertex_offset += obj.tot_uv_vertices();
210 offsets.normal_offset += obj.get_normal_coords().size();
211 }
212
213 /* Parallel over meshes: main result writing. */
215 for (const int i : range) {
216 OBJMesh &obj = *exportable_as_mesh[i];
217 auto &fh = buffers[i];
218
219 obj_writer.write_object_name(fh, obj);
220 obj_writer.write_vertex_coords(fh, obj, export_params.export_colors);
221
222 if (obj.tot_faces() > 0) {
223 if (export_params.export_smooth_groups) {
224 obj.calc_smooth_groups(export_params.smooth_groups_bitflags);
225 }
226 if (export_params.export_materials) {
227 obj.calc_face_order();
228 }
229 if (export_params.export_normals) {
230 obj_writer.write_normals(fh, obj);
231 }
232 if (export_params.export_uv) {
233 obj_writer.write_uv_coords(fh, obj);
234 }
235 /* This function takes a 0-indexed slot index for the obj_mesh object and
236 * returns the material name that we are using in the `.obj` file for it. */
237 const auto *obj_mtlindices = mtlindices.is_empty() ? nullptr : &mtlindices[i];
238 auto matname_fn = [&](int s) -> const char * {
239 if (!obj_mtlindices || s < 0 || s >= obj_mtlindices->size()) {
240 return nullptr;
241 }
242 return mtl_writer->mtlmaterial_name((*obj_mtlindices)[s]);
243 };
244 obj_writer.write_face_elements(fh, index_offsets[i], obj, matname_fn);
245 }
246 obj_writer.write_edges_indices(fh, index_offsets[i], obj);
247
248 /* Nothing will need this object's data after this point, release
249 * various arrays here. */
250 obj.clear();
251 }
252 });
253
254 /* Write all the object text buffers into the output file. */
255 FILE *f = obj_writer.get_outfile();
256 for (auto &b : buffers) {
257 b.write_to_file(f);
258 }
259}
260
264static void write_nurbs_curve_objects(const Span<std::unique_ptr<IOBJCurve>> exportable_as_nurbs,
265 const OBJWriter &obj_writer)
266{
267 FormatHandler fh;
268 /* #OBJCurve doesn't have any dynamically allocated memory, so it's fine
269 * to wait for #blender::Vector to clean the objects up. */
270 for (const std::unique_ptr<IOBJCurve> &obj_curve : exportable_as_nurbs) {
271 obj_writer.write_nurbs_curve(fh, *obj_curve);
272 }
273 fh.write_to_file(obj_writer.get_outfile());
274}
275
276static bool open_stream_writers(const OBJExportParams &export_params,
277 const char *filepath,
278 std::unique_ptr<OBJWriter> &r_frame_writer,
279 std::unique_ptr<MTLWriter> &r_mtl_writer)
280{
281 try {
282 r_frame_writer = std::make_unique<OBJWriter>(filepath, export_params);
283 }
284 catch (const std::system_error &ex) {
286 BKE_reportf(export_params.reports, RPT_ERROR, "OBJ Export: Cannot open file '%s'", filepath);
287 return false;
288 }
289 if (!r_frame_writer) {
290 BLI_assert_msg(false, "File should be writable by now.");
291 return false;
292 }
293 if (export_params.export_materials || export_params.export_material_groups) {
294 try {
295 r_mtl_writer = std::make_unique<MTLWriter>(filepath, export_params.export_materials);
296 }
297 catch (const std::system_error &ex) {
299 BKE_reportf(export_params.reports,
301 "OBJ Export: Cannot create mtl file for '%s'",
302 filepath);
303 }
304 }
305 return true;
306}
307
308static void write_materials(MTLWriter *mtl_writer, const OBJExportParams &export_params)
309{
310 BLI_assert(mtl_writer);
311 mtl_writer->write_header(export_params.blen_filepath);
312 char dest_dir[FILE_MAX];
313 if (export_params.file_base_for_tests[0] == '\0') {
314 BLI_path_split_dir_part(export_params.filepath, dest_dir, sizeof(dest_dir));
315 }
316 else {
317 STRNCPY(dest_dir, export_params.file_base_for_tests);
318 }
319 BLI_path_slash_native(dest_dir);
320 BLI_path_normalize(dest_dir);
321 mtl_writer->write_materials(export_params.blen_filepath,
322 export_params.path_mode,
323 dest_dir,
324 export_params.export_pbr_extensions);
325}
326
327void export_objects(const OBJExportParams &export_params,
328 const Span<std::unique_ptr<OBJMesh>> meshes,
329 const Span<std::unique_ptr<IOBJCurve>> curves,
330 const char *filepath)
331{
332 /* Open */
333 std::unique_ptr<OBJWriter> obj_writer;
334 std::unique_ptr<MTLWriter> mtl_writer;
335 if (!open_stream_writers(export_params, filepath, obj_writer, mtl_writer)) {
336 return;
337 }
338
339 /* Write */
340 obj_writer->write_header();
341 write_mesh_objects(meshes, *obj_writer, mtl_writer.get(), export_params);
342 write_nurbs_curve_objects(curves, *obj_writer);
343 if (mtl_writer && export_params.export_materials) {
344 write_materials(mtl_writer.get(), export_params);
345 }
346}
347
348void export_frame(Depsgraph *depsgraph, const OBJExportParams &export_params, const char *filepath)
349{
350 auto [exportable_as_mesh, exportable_as_nurbs] = filter_supported_objects(depsgraph,
351 export_params);
352
353 if (exportable_as_mesh.size() == 0 && exportable_as_nurbs.size() == 0) {
354 BKE_reportf(export_params.reports, RPT_WARNING, "OBJ Export: No information to write");
355 return;
356 }
357
358 export_objects(export_params, exportable_as_mesh, exportable_as_nurbs, filepath);
359}
360
361bool append_frame_to_filename(const char *filepath,
362 const int frame,
363 char r_filepath_with_frames[1024])
364{
365 BLI_strncpy(r_filepath_with_frames, filepath, FILE_MAX);
366 BLI_path_extension_strip(r_filepath_with_frames);
367 BLI_path_frame(r_filepath_with_frames, FILE_MAX, frame, 4);
368 return BLI_path_extension_replace(r_filepath_with_frames, FILE_MAX, ".obj");
369}
370
371void exporter_main(bContext *C, const OBJExportParams &export_params)
372{
374
375 Collection *collection = nullptr;
376 if (export_params.collection[0]) {
377 Main *bmain = CTX_data_main(C);
378 collection = reinterpret_cast<Collection *>(
379 BKE_libblock_find_name(bmain, ID_GR, export_params.collection));
380 if (!collection) {
381 BKE_reportf(export_params.reports,
382 RPT_ERROR,
383 "OBJ Export: Unable to find collection '%s'",
384 export_params.collection);
385 return;
386 }
387 }
388
389 OBJDepsgraph obj_depsgraph(C, export_params.export_eval_mode, collection);
390 Scene *scene = DEG_get_input_scene(obj_depsgraph.get());
391 const char *filepath = export_params.filepath;
392
393 /* Single frame export, i.e. no animation. */
394 if (!export_params.export_animation) {
395 fmt::println("Writing to {}", filepath);
396 export_frame(obj_depsgraph.get(), export_params, filepath);
397 return;
398 }
399
400 char filepath_with_frames[FILE_MAX];
401 /* Used to reset the Scene to its original state. */
402 const int original_frame = scene->r.cfra;
403
404 for (int frame = export_params.start_frame; frame <= export_params.end_frame; frame++) {
405 const bool filepath_ok = append_frame_to_filename(filepath, frame, filepath_with_frames);
406 if (!filepath_ok) {
407 CLOG_ERROR(&LOG, "File Path too long: %s", filepath_with_frames);
408 return;
409 }
410
411 scene->r.cfra = frame;
412 obj_depsgraph.update_for_newframe();
413 fmt::println("Writing to {}", filepath_with_frames);
414 export_frame(obj_depsgraph.get(), export_params, filepath_with_frames);
415 }
416 scene->r.cfra = original_frame;
417}
418} // namespace blender::io::obj
Depsgraph * CTX_data_ensure_evaluated_depsgraph(const bContext *C)
Scene * CTX_data_scene(const bContext *C)
Main * CTX_data_main(const bContext *C)
ViewLayer * CTX_data_view_layer(const bContext *C)
ID * BKE_libblock_find_name(Main *bmain, short type, const char *name, const std::optional< Library * > lib=std::nullopt) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition lib_id.cc:1710
void BKE_reportf(ReportList *reports, eReportType type, const char *format,...) ATTR_PRINTF_FORMAT(3
@ RPT_ERROR
Definition BKE_report.hh:39
@ RPT_WARNING
Definition BKE_report.hh:38
void BKE_scene_graph_evaluated_ensure(Depsgraph *depsgraph, Main *bmain)
Definition scene.cc:2626
void BKE_scene_graph_update_for_newframe(Depsgraph *depsgraph)
Definition scene.cc:2700
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
bool bool BLI_path_extension_strip(char *path) ATTR_NONNULL(1)
#define FILE_MAX
bool BLI_path_extension_replace(char *path, size_t path_maxncpy, const char *ext) ATTR_NONNULL(1
void BLI_path_slash_native(char *path) ATTR_NONNULL(1)
int BLI_path_normalize(char *path) ATTR_NONNULL(1)
void void BLI_path_split_dir_part(const char *filepath, char *dir, size_t dir_maxncpy) ATTR_NONNULL(1
bool BLI_path_frame(char *path, size_t path_maxncpy, int frame, int digits) ATTR_NONNULL(1)
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:693
char * BLI_strncpy(char *__restrict dst, const char *__restrict src, size_t dst_maxncpy) ATTR_NONNULL(1
#define CLOG_ERROR(clg_ref,...)
Definition CLG_log.h:188
eEvaluationMode
@ DAG_EVAL_RENDER
Depsgraph * DEG_graph_new(Main *bmain, Scene *scene, ViewLayer *view_layer, eEvaluationMode mode)
Definition depsgraph.cc:278
void DEG_graph_free(Depsgraph *graph)
Definition depsgraph.cc:306
void DEG_graph_build_from_collection(Depsgraph *graph, Collection *collection)
void DEG_graph_build_for_all_objects(Depsgraph *graph)
#define DEG_OBJECT_ITER_BEGIN(settings_, instance_)
#define DEG_OBJECT_ITER_END
Scene * DEG_get_input_scene(const Depsgraph *graph)
@ DEG_ITER_OBJECT_FLAG_LINKED_DIRECTLY
@ DEG_ITER_OBJECT_FLAG_VISIBLE
@ DEG_ITER_OBJECT_FLAG_DUPLI
@ DEG_ITER_OBJECT_FLAG_LINKED_VIA_SET
@ ID_GR
Object groups, one object can be in many groups at once.
@ CU_BEZIER
@ OB_MODE_OBJECT
@ OB_SURF
@ OB_MESH
@ OB_CURVES_LEGACY
#define BASE_SELECTED(v3d, base)
#define C
Definition RandGen.cpp:29
BPy_StructRNA * depsgraph
void append(const T &value)
bool is_empty() const
void reserve(const int64_t min_capacity)
Vector< int > add_materials(const OBJMesh &mesh_to_export)
void write_header(const char *blen_filepath)
void write_materials(const char *blen_filepath, ePathReferenceMode path_mode, const char *dest_dir, bool write_pbr)
const char * mtlmaterial_name(int index)
OBJDepsgraph(const bContext *C, eEvaluationMode eval_mode, Collection *collection)
void write_normals(FormatHandler &fh, OBJMesh &obj_mesh_data)
void write_mtllib_name(StringRefNull mtl_filepath) const
void write_uv_coords(FormatHandler &fh, OBJMesh &obj_mesh_data) const
void write_nurbs_curve(FormatHandler &fh, const IOBJCurve &obj_nurbs_data) const
void write_face_elements(FormatHandler &fh, const IndexOffsets &offsets, const OBJMesh &obj_mesh_data, FunctionRef< const char *(int)> matname_fn)
void write_vertex_coords(FormatHandler &fh, const OBJMesh &obj_mesh_data, bool write_colors) const
void write_edges_indices(FormatHandler &fh, const IndexOffsets &offsets, const OBJMesh &obj_mesh_data) const
void write_object_name(FormatHandler &fh, const OBJMesh &obj_mesh_data) const
int count
#define LOG(level)
Definition log.h:97
bool mode_set(bContext *C, eObjectMode mode)
static void print_exception_error(const std::system_error &ex)
bool append_frame_to_filename(const char *filepath, const int frame, char r_filepath_with_frames[1024])
static void write_materials(MTLWriter *mtl_writer, const OBJExportParams &export_params)
void export_objects(const OBJExportParams &export_params, const Span< std::unique_ptr< OBJMesh > > meshes, const Span< std::unique_ptr< IOBJCurve > > curves, const char *filepath)
std::pair< Vector< std::unique_ptr< OBJMesh > >, Vector< std::unique_ptr< IOBJCurve > > > filter_supported_objects(Depsgraph *depsgraph, const OBJExportParams &export_params)
static bool open_stream_writers(const OBJExportParams &export_params, const char *filepath, std::unique_ptr< OBJWriter > &r_frame_writer, std::unique_ptr< MTLWriter > &r_mtl_writer)
static bool is_curve_nurbs_compatible(const Nurb *nurb)
static void write_mesh_objects(const Span< std::unique_ptr< OBJMesh > > exportable_as_mesh, OBJWriter &obj_writer, MTLWriter *mtl_writer, const OBJExportParams &export_params)
static void write_nurbs_curve_objects(const Span< std::unique_ptr< IOBJCurve > > exportable_as_nurbs, const OBJWriter &obj_writer)
void exporter_main(bContext *C, const OBJExportParams &export_params)
void export_frame(Depsgraph *depsgraph, const OBJExportParams &export_params, const char *filepath)
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
Definition BLI_task.hh:93
ListBase nurb
void * first
struct Nurb * next
short type
eEvaluationMode export_eval_mode
const char * blen_filepath
char file_base_for_tests[FILE_MAX]
ReportList * reports
char collection[MAX_ID_NAME - 2]
ePathReferenceMode path_mode
char filepath[FILE_MAX]
struct RenderData r
i
Definition text_draw.cc:230
char * buffers[2]