Blender V4.3
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
9#include <cstdio>
10#include <memory>
11#include <system_error>
12
13#include "BKE_context.hh"
14#include "BKE_lib_id.hh"
15#include "BKE_report.hh"
16#include "BKE_scene.hh"
17
18#include "BLI_path_utils.hh"
19#include "BLI_string.h"
20#include "BLI_task.hh"
21#include "BLI_vector.hh"
22
24
26#include "DNA_scene_types.h"
27
28#include "ED_object.hh"
29
30#include "obj_export_mesh.hh"
31#include "obj_export_nurbs.hh"
32#include "obj_exporter.hh"
33
35
36namespace blender::io::obj {
37
39 const eEvaluationMode eval_mode,
40 Collection *collection)
41{
42 Scene *scene = CTX_data_scene(C);
43 Main *bmain = CTX_data_main(C);
44 ViewLayer *view_layer = CTX_data_view_layer(C);
45
46 /* If a collection was provided, use it. */
47 if (collection) {
48 depsgraph_ = DEG_graph_new(bmain, scene, view_layer, eval_mode);
49 needs_free_ = true;
50 DEG_graph_build_from_collection(depsgraph_, collection);
51 BKE_scene_graph_evaluated_ensure(depsgraph_, bmain);
52 }
53 else if (eval_mode == DAG_EVAL_RENDER) {
54 depsgraph_ = DEG_graph_new(bmain, scene, view_layer, eval_mode);
55 needs_free_ = true;
57 BKE_scene_graph_evaluated_ensure(depsgraph_, bmain);
58 }
59 else {
61 needs_free_ = false;
62 }
63}
64
66{
67 if (needs_free_) {
68 DEG_graph_free(depsgraph_);
69 }
70}
71
73{
74 return depsgraph_;
75}
76
81
82static void print_exception_error(const std::system_error &ex)
83{
84 std::cerr << ex.code().category().name() << ": " << ex.what() << ": " << ex.code().message()
85 << std::endl;
86}
87
88static bool is_curve_nurbs_compatible(const Nurb *nurb)
89{
90 while (nurb) {
91 if (nurb->type == CU_BEZIER || nurb->pntsv != 1) {
92 return false;
93 }
94 nurb = nurb->next;
95 }
96 return true;
97}
98
104std::pair<Vector<std::unique_ptr<OBJMesh>>, Vector<std::unique_ptr<OBJCurve>>>
105filter_supported_objects(Depsgraph *depsgraph, const OBJExportParams &export_params)
106{
107 Vector<std::unique_ptr<OBJMesh>> r_exportable_meshes;
108 Vector<std::unique_ptr<OBJCurve>> r_exportable_nurbs;
109 DEGObjectIterSettings deg_iter_settings{};
110 deg_iter_settings.depsgraph = depsgraph;
111 deg_iter_settings.flags = DEG_ITER_OBJECT_FLAG_LINKED_DIRECTLY |
114 DEG_OBJECT_ITER_BEGIN (&deg_iter_settings, object) {
115 if (export_params.export_selected_objects && !(object->base_flag & BASE_SELECTED)) {
116 continue;
117 }
118 switch (object->type) {
119 case OB_SURF:
120 /* Evaluated surface objects appear as mesh objects from the iterator. */
121 break;
122 case OB_MESH:
123 r_exportable_meshes.append(std::make_unique<OBJMesh>(depsgraph, export_params, object));
124 break;
125 case OB_CURVES_LEGACY: {
126 Curve *curve = static_cast<Curve *>(object->data);
127 Nurb *nurb{static_cast<Nurb *>(curve->nurb.first)};
128 if (!nurb) {
129 /* An empty curve. Not yet supported to export these as meshes. */
130 if (export_params.export_curves_as_nurbs) {
131 r_exportable_nurbs.append(
132 std::make_unique<OBJCurve>(depsgraph, export_params, object));
133 }
134 break;
135 }
136 if (export_params.export_curves_as_nurbs && is_curve_nurbs_compatible(nurb)) {
137 /* Export in parameter form: control points. */
138 r_exportable_nurbs.append(std::make_unique<OBJCurve>(depsgraph, export_params, object));
139 }
140 else {
141 /* Export in mesh form: edges and vertices. */
142 r_exportable_meshes.append(std::make_unique<OBJMesh>(depsgraph, export_params, object));
143 }
144 break;
145 }
146 default:
147 /* Other object types are not supported. */
148 break;
149 }
150 }
152 return {std::move(r_exportable_meshes), std::move(r_exportable_nurbs)};
153}
154
155static void write_mesh_objects(const Span<std::unique_ptr<OBJMesh>> exportable_as_mesh,
156 OBJWriter &obj_writer,
157 MTLWriter *mtl_writer,
158 const OBJExportParams &export_params)
159{
160 /* Parallelization is over meshes/objects, which means
161 * we have to have the output text buffer for each object,
162 * and write them all into the file at the end. */
163 size_t count = exportable_as_mesh.size();
165
166 /* Serial: gather material indices, ensure normals & edges. */
167 Vector<Vector<int>> mtlindices;
168 if (mtl_writer) {
169 if (export_params.export_materials) {
170 obj_writer.write_mtllib_name(mtl_writer->mtl_file_path());
171 }
172 mtlindices.reserve(count);
173 }
174 for (auto &obj_mesh : exportable_as_mesh) {
175 OBJMesh &obj = *obj_mesh;
176 if (mtl_writer) {
177 mtlindices.append(mtl_writer->add_materials(obj));
178 }
179 }
180
181 /* Parallel over meshes: store normal coords & indices, uv coords and indices. */
183 for (const int i : range) {
184 OBJMesh &obj = *exportable_as_mesh[i];
185 if (export_params.export_normals) {
187 }
188 if (export_params.export_uv) {
190 }
191 }
192 });
193
194 /* Serial: calculate index offsets; these are sequentially added
195 * over all meshes, and requite normal/uv indices to be calculated. */
196 Vector<IndexOffsets> index_offsets;
197 index_offsets.reserve(count);
198 IndexOffsets offsets{0, 0, 0};
199 for (auto &obj_mesh : exportable_as_mesh) {
200 OBJMesh &obj = *obj_mesh;
201 index_offsets.append(offsets);
202 offsets.vertex_offset += obj.tot_vertices();
203 offsets.uv_vertex_offset += obj.tot_uv_vertices();
204 offsets.normal_offset += obj.get_normal_coords().size();
205 }
206
207 /* Parallel over meshes: main result writing. */
209 for (const int i : range) {
210 OBJMesh &obj = *exportable_as_mesh[i];
211 auto &fh = buffers[i];
212
213 obj_writer.write_object_name(fh, obj);
214 obj_writer.write_vertex_coords(fh, obj, export_params.export_colors);
215
216 if (obj.tot_faces() > 0) {
217 if (export_params.export_smooth_groups) {
219 }
220 if (export_params.export_materials) {
221 obj.calc_face_order();
222 }
223 if (export_params.export_normals) {
224 obj_writer.write_normals(fh, obj);
225 }
226 if (export_params.export_uv) {
227 obj_writer.write_uv_coords(fh, obj);
228 }
229 /* This function takes a 0-indexed slot index for the obj_mesh object and
230 * returns the material name that we are using in the `.obj` file for it. */
231 const auto *obj_mtlindices = mtlindices.is_empty() ? nullptr : &mtlindices[i];
232 auto matname_fn = [&](int s) -> const char * {
233 if (!obj_mtlindices || s < 0 || s >= obj_mtlindices->size()) {
234 return nullptr;
235 }
236 return mtl_writer->mtlmaterial_name((*obj_mtlindices)[s]);
237 };
238 obj_writer.write_face_elements(fh, index_offsets[i], obj, matname_fn);
239 }
240 obj_writer.write_edges_indices(fh, index_offsets[i], obj);
241
242 /* Nothing will need this object's data after this point, release
243 * various arrays here. */
244 obj.clear();
245 }
246 });
247
248 /* Write all the object text buffers into the output file. */
249 FILE *f = obj_writer.get_outfile();
250 for (auto &b : buffers) {
251 b.write_to_file(f);
252 }
253}
254
258static void write_nurbs_curve_objects(const Span<std::unique_ptr<OBJCurve>> exportable_as_nurbs,
259 const OBJWriter &obj_writer)
260{
261 FormatHandler fh;
262 /* #OBJCurve doesn't have any dynamically allocated memory, so it's fine
263 * to wait for #blender::Vector to clean the objects up. */
264 for (const std::unique_ptr<OBJCurve> &obj_curve : exportable_as_nurbs) {
265 obj_writer.write_nurbs_curve(fh, *obj_curve);
266 }
267 fh.write_to_file(obj_writer.get_outfile());
268}
269
270void export_frame(Depsgraph *depsgraph, const OBJExportParams &export_params, const char *filepath)
271{
272 std::unique_ptr<OBJWriter> frame_writer = nullptr;
273 try {
274 frame_writer = std::make_unique<OBJWriter>(filepath, export_params);
275 }
276 catch (const std::system_error &ex) {
278 BKE_reportf(export_params.reports, RPT_ERROR, "OBJ Export: Cannot open file '%s'", filepath);
279 return;
280 }
281 if (!frame_writer) {
282 BLI_assert_msg(false, "File should be writable by now.");
283 return;
284 }
285 std::unique_ptr<MTLWriter> mtl_writer = nullptr;
286 if (export_params.export_materials || export_params.export_material_groups) {
287 try {
288 mtl_writer = std::make_unique<MTLWriter>(filepath, export_params.export_materials);
289 }
290 catch (const std::system_error &ex) {
292 BKE_reportf(export_params.reports,
294 "OBJ Export: Cannot create mtl file for '%s'",
295 filepath);
296 }
297 }
298
299 frame_writer->write_header();
300
301 auto [exportable_as_mesh, exportable_as_nurbs] = filter_supported_objects(depsgraph,
302 export_params);
303
304 write_mesh_objects(exportable_as_mesh, *frame_writer, mtl_writer.get(), export_params);
305 if (mtl_writer && export_params.export_materials) {
306 mtl_writer->write_header(export_params.blen_filepath);
307 char dest_dir[FILE_MAX];
308 if (export_params.file_base_for_tests[0] == '\0') {
309 BLI_path_split_dir_part(export_params.filepath, dest_dir, sizeof(dest_dir));
310 }
311 else {
312 STRNCPY(dest_dir, export_params.file_base_for_tests);
313 }
314 BLI_path_slash_native(dest_dir);
315 BLI_path_normalize(dest_dir);
316 mtl_writer->write_materials(export_params.blen_filepath,
317 export_params.path_mode,
318 dest_dir,
319 export_params.export_pbr_extensions);
320 }
321 write_nurbs_curve_objects(exportable_as_nurbs, *frame_writer);
322}
323
324bool append_frame_to_filename(const char *filepath,
325 const int frame,
326 char r_filepath_with_frames[1024])
327{
328 BLI_strncpy(r_filepath_with_frames, filepath, FILE_MAX);
329 BLI_path_extension_strip(r_filepath_with_frames);
330 BLI_path_frame(r_filepath_with_frames, FILE_MAX, frame, 4);
331 return BLI_path_extension_replace(r_filepath_with_frames, FILE_MAX, ".obj");
332}
333
334void exporter_main(bContext *C, const OBJExportParams &export_params)
335{
337
338 Collection *collection = nullptr;
339 if (export_params.collection[0]) {
340 Main *bmain = CTX_data_main(C);
341 collection = reinterpret_cast<Collection *>(
342 BKE_libblock_find_name(bmain, ID_GR, export_params.collection));
343 if (!collection) {
344 BKE_reportf(export_params.reports,
345 RPT_ERROR,
346 "OBJ Export: Unable to find collection '%s'",
347 export_params.collection);
348 return;
349 }
350 }
351
352 OBJDepsgraph obj_depsgraph(C, export_params.export_eval_mode, collection);
353 Scene *scene = DEG_get_input_scene(obj_depsgraph.get());
354 const char *filepath = export_params.filepath;
355
356 /* Single frame export, i.e. no animation. */
357 if (!export_params.export_animation) {
358 fprintf(stderr, "Writing to %s\n", filepath);
359 export_frame(obj_depsgraph.get(), export_params, filepath);
360 return;
361 }
362
363 char filepath_with_frames[FILE_MAX];
364 /* Used to reset the Scene to its original state. */
365 const int original_frame = scene->r.cfra;
366
367 for (int frame = export_params.start_frame; frame <= export_params.end_frame; frame++) {
368 const bool filepath_ok = append_frame_to_filename(filepath, frame, filepath_with_frames);
369 if (!filepath_ok) {
370 fprintf(stderr, "Error: File Path too long.\n%s\n", filepath_with_frames);
371 return;
372 }
373
374 scene->r.cfra = frame;
375 obj_depsgraph.update_for_newframe();
376 fprintf(stderr, "Writing to %s\n", filepath_with_frames);
377 export_frame(obj_depsgraph.get(), export_params, filepath_with_frames);
378 }
379 scene->r.cfra = original_frame;
380}
381} // 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:1657
void BKE_reportf(ReportList *reports, eReportType type, const char *format,...) ATTR_PRINTF_FORMAT(3
void BKE_scene_graph_evaluated_ensure(Depsgraph *depsgraph, Main *bmain)
Definition scene.cc:2573
void BKE_scene_graph_update_for_newframe(Depsgraph *depsgraph)
Definition scene.cc:2647
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:57
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)
#define STRNCPY(dst, src)
Definition BLI_string.h:593
char * BLI_strncpy(char *__restrict dst, const char *__restrict src, size_t dst_maxncpy) ATTR_NONNULL(1
eEvaluationMode
@ DAG_EVAL_RENDER
Depsgraph * DEG_graph_new(Main *bmain, Scene *scene, ViewLayer *view_layer, eEvaluationMode mode)
Definition depsgraph.cc:273
void DEG_graph_free(Depsgraph *graph)
Definition depsgraph.cc:301
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)
constexpr int64_t size() const
Definition BLI_span.hh:253
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)
const char * mtlmaterial_name(int index)
OBJDepsgraph(const bContext *C, eEvaluationMode eval_mode, Collection *collection)
void calc_smooth_groups(bool use_bitflags)
Span< float3 > get_normal_coords() const
void write_normals(FormatHandler &fh, OBJMesh &obj_mesh_data)
void write_uv_coords(FormatHandler &fh, OBJMesh &obj_mesh_data) const
void write_mtllib_name(const StringRefNull mtl_filepath) const
void write_nurbs_curve(FormatHandler &fh, const OBJCurve &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
local_group_size(16, 16) .push_constant(Type b
const Depsgraph * depsgraph
IndexRange range
int count
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 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< OBJCurve > > exportable_as_nurbs, const OBJWriter &obj_writer)
std::pair< Vector< std::unique_ptr< OBJMesh > >, Vector< std::unique_ptr< OBJCurve > > > filter_supported_objects(Depsgraph *depsgraph, const OBJExportParams &export_params)
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:95
struct Nurb * next
short type
char collection[MAX_IDPROP_NAME]
eEvaluationMode export_eval_mode
const char * blen_filepath
char file_base_for_tests[FILE_MAX]
ReportList * reports
ePathReferenceMode path_mode
char filepath[FILE_MAX]
char * buffers[2]