Blender V4.5
obj_export_file_writer.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 <algorithm>
10#include <system_error>
11
12#include "BKE_attribute.hh"
13#include "BKE_blender_version.h"
14#include "BKE_mesh.hh"
15
16#include "BLI_color.hh"
18#include "BLI_fileops.h"
19#include "BLI_math_matrix.hh"
20#include "BLI_path_utils.hh"
21#include "BLI_string.h"
22#include "BLI_task.hh"
23
24#include "IO_path_util.hh"
25
26#include "obj_export_mesh.hh"
27#include "obj_export_mtl.hh"
28#include "obj_export_nurbs.hh"
29
31
32#include "CLG_log.h"
33static CLG_LogRef LOG = {"io.obj"};
34
45
46static const char *DEFORM_GROUP_DISABLED = "off";
47/* There is no deform group default name. Use what the user set in the UI. */
48
54static const char *MATERIAL_GROUP_DISABLED = "";
55
56OBJWriter::OBJWriter(const char *filepath, const OBJExportParams &export_params) noexcept(false)
57 : export_params_(export_params), outfile_path_(filepath), outfile_(nullptr)
58{
59 outfile_ = BLI_fopen(filepath, "wb");
60 if (!outfile_) {
61 throw std::system_error(errno, std::system_category(), "Cannot open file " + outfile_path_);
62 }
63}
65{
66 if (outfile_ && std::fclose(outfile_)) {
68 "Error: could not close file '%s' properly, it may be corrupted.",
69 outfile_path_.c_str());
70 }
71}
72
73void OBJWriter::write_vert_uv_normal_indices(FormatHandler &fh,
74 const IndexOffsets &offsets,
75 Span<int> vert_indices,
76 Span<int> uv_indices,
77 Span<int> normal_indices,
78 bool flip) const
79{
80 BLI_assert(vert_indices.size() == uv_indices.size() &&
81 vert_indices.size() == normal_indices.size());
82 const int vertex_offset = offsets.vertex_offset + 1;
83 const int uv_offset = offsets.uv_vertex_offset + 1;
84 const int normal_offset = offsets.normal_offset + 1;
85 const int n = vert_indices.size();
87 if (!flip) {
88 for (int j = 0; j < n; ++j) {
89 fh.write_obj_face_v_uv_normal(vert_indices[j] + vertex_offset,
90 uv_indices[j] + uv_offset,
91 normal_indices[j] + normal_offset);
92 }
93 }
94 else {
95 /* For a transform that is mirrored (negative scale on odd number of axes),
96 * we want to flip the face index order. Start from the same index, and
97 * then go backwards. Same logic in other write_*_indices functions below. */
98 for (int k = 0; k < n; ++k) {
99 int j = k == 0 ? 0 : n - k;
100 fh.write_obj_face_v_uv_normal(vert_indices[j] + vertex_offset,
101 uv_indices[j] + uv_offset,
102 normal_indices[j] + normal_offset);
103 }
104 }
106}
107
108void OBJWriter::write_vert_normal_indices(FormatHandler &fh,
109 const IndexOffsets &offsets,
110 Span<int> vert_indices,
111 Span<int> /*uv_indices*/,
112 Span<int> normal_indices,
113 bool flip) const
114{
115 BLI_assert(vert_indices.size() == normal_indices.size());
116 const int vertex_offset = offsets.vertex_offset + 1;
117 const int normal_offset = offsets.normal_offset + 1;
118 const int n = vert_indices.size();
119 fh.write_obj_face_begin();
120 if (!flip) {
121 for (int j = 0; j < n; ++j) {
122 fh.write_obj_face_v_normal(vert_indices[j] + vertex_offset,
123 normal_indices[j] + normal_offset);
124 }
125 }
126 else {
127 for (int k = 0; k < n; ++k) {
128 int j = k == 0 ? 0 : n - k;
129 fh.write_obj_face_v_normal(vert_indices[j] + vertex_offset,
130 normal_indices[j] + normal_offset);
131 }
132 }
133 fh.write_obj_face_end();
134}
135
136void OBJWriter::write_vert_uv_indices(FormatHandler &fh,
137 const IndexOffsets &offsets,
138 Span<int> vert_indices,
139 Span<int> uv_indices,
140 Span<int> /*normal_indices*/,
141 bool flip) const
142{
143 BLI_assert(vert_indices.size() == uv_indices.size());
144 const int vertex_offset = offsets.vertex_offset + 1;
145 const int uv_offset = offsets.uv_vertex_offset + 1;
146 const int n = vert_indices.size();
147 fh.write_obj_face_begin();
148 if (!flip) {
149 for (int j = 0; j < n; ++j) {
150 fh.write_obj_face_v_uv(vert_indices[j] + vertex_offset, uv_indices[j] + uv_offset);
151 }
152 }
153 else {
154 for (int k = 0; k < n; ++k) {
155 int j = k == 0 ? 0 : n - k;
156 fh.write_obj_face_v_uv(vert_indices[j] + vertex_offset, uv_indices[j] + uv_offset);
157 }
158 }
159 fh.write_obj_face_end();
160}
161
162void OBJWriter::write_vert_indices(FormatHandler &fh,
163 const IndexOffsets &offsets,
164 Span<int> vert_indices,
165 Span<int> /*uv_indices*/,
166 Span<int> /*normal_indices*/,
167 bool flip) const
168{
169 const int vertex_offset = offsets.vertex_offset + 1;
170 const int n = vert_indices.size();
171 fh.write_obj_face_begin();
172 if (!flip) {
173 for (int j = 0; j < n; ++j) {
174 fh.write_obj_face_v(vert_indices[j] + vertex_offset);
175 }
176 }
177 else {
178 for (int k = 0; k < n; ++k) {
179 int j = k == 0 ? 0 : n - k;
180 fh.write_obj_face_v(vert_indices[j] + vertex_offset);
181 }
182 }
183 fh.write_obj_face_end();
184}
185
187{
188 using namespace std::string_literals;
189 FormatHandler fh;
190 fh.write_string("# Blender "s + BKE_blender_version_string());
191 fh.write_string("# www.blender.org");
192 fh.write_to_file(outfile_);
193}
194
195void OBJWriter::write_mtllib_name(const StringRefNull mtl_filepath) const
196{
197 /* Split `.MTL` file path into parent directory and filename. */
198 char mtl_file_name[FILE_MAXFILE];
199 char mtl_dir_name[FILE_MAXDIR];
200 BLI_path_split_dir_file(mtl_filepath.data(),
201 mtl_dir_name,
202 sizeof(mtl_dir_name),
203 mtl_file_name,
204 sizeof(mtl_file_name));
205 FormatHandler fh;
206 fh.write_obj_mtllib(mtl_file_name);
207 fh.write_to_file(outfile_);
208}
209
210static void spaces_to_underscores(std::string &r_name)
211{
212 std::replace(r_name.begin(), r_name.end(), ' ', '_');
213}
214
215void OBJWriter::write_object_name(FormatHandler &fh, const OBJMesh &obj_mesh_data) const
216{
217 std::string object_name = obj_mesh_data.get_object_name();
218 spaces_to_underscores(object_name);
219 if (export_params_.export_object_groups) {
220 std::string mesh_name = obj_mesh_data.get_object_mesh_name();
221 spaces_to_underscores(mesh_name);
222 fh.write_obj_group(object_name + "_" + mesh_name);
223 return;
224 }
225 fh.write_obj_object(object_name);
226}
227
228/* Split up large meshes into multi-threaded jobs; each job processes
229 * this amount of items. */
230static const int chunk_size = 32768;
231static int calc_chunk_count(int count)
232{
233 return (count + chunk_size - 1) / chunk_size;
234}
235
236/* Write /tot_count/ items to OBJ file output. Each item is written
237 * by a /function/ that should be independent from other items.
238 * If the amount of items is large enough (> chunk_size), then writing
239 * will be done in parallel, into temporary FormatHandler buffers that
240 * will be written into the final /fh/ buffer at the end.
241 */
242template<typename Function>
243void obj_parallel_chunked_output(FormatHandler &fh, int tot_count, const Function &function)
244{
245 if (tot_count <= 0) {
246 return;
247 }
248 /* If we have just one chunk, process it directly into the output
249 * buffer - avoids all the job scheduling and temporary vector allocation
250 * overhead. */
251 const int chunk_count = calc_chunk_count(tot_count);
252 if (chunk_count == 1) {
253 for (int i = 0; i < tot_count; i++) {
254 function(fh, i);
255 }
256 return;
257 }
258 /* Give each chunk its own temporary output buffer, and process them in parallel. */
259 Array<FormatHandler> buffers(chunk_count);
260 threading::parallel_for(IndexRange(chunk_count), 1, [&](IndexRange range) {
261 for (const int r : range) {
262 int i_start = r * chunk_size;
263 int i_end = std::min(i_start + chunk_size, tot_count);
264 auto &buf = buffers[r];
265 for (int i = i_start; i < i_end; i++) {
266 function(buf, i);
267 }
268 }
269 });
270 /* Emit all temporary output buffers into the destination buffer. */
271 for (auto &buf : buffers) {
272 fh.append_from(buf);
273 }
274}
275
277 const OBJMesh &obj_mesh_data,
278 bool write_colors) const
279{
280 const int tot_count = obj_mesh_data.tot_vertices();
281
282 const Mesh *mesh = obj_mesh_data.get_mesh();
283 const StringRef name = mesh->active_color_attribute;
284
285 const float4x4 transform = obj_mesh_data.get_world_axes_transform();
286 const Span<float3> positions = obj_mesh_data.get_mesh()->vert_positions();
287
288 if (write_colors && !name.is_empty()) {
289 const bke::AttributeAccessor attributes = mesh->attributes();
290 const VArray<ColorGeometry4f> attribute = *attributes.lookup_or_default<ColorGeometry4f>(
291 name, bke::AttrDomain::Point, {0.0f, 0.0f, 0.0f, 0.0f});
292
293 BLI_assert(tot_count == attribute.size());
294 obj_parallel_chunked_output(fh, tot_count, [&](FormatHandler &buf, int i) {
295 const float3 vertex = math::transform_point(transform, positions[i]);
296 ColorGeometry4f linear = attribute.get(i);
297 float srgb[3];
298 linearrgb_to_srgb_v3_v3(srgb, linear);
299 buf.write_obj_vertex_color(vertex[0], vertex[1], vertex[2], srgb[0], srgb[1], srgb[2]);
300 });
301 }
302 else {
303 obj_parallel_chunked_output(fh, tot_count, [&](FormatHandler &buf, int i) {
304 const float3 vertex = math::transform_point(transform, positions[i]);
305 buf.write_obj_vertex(vertex[0], vertex[1], vertex[2]);
306 });
307 }
308}
309
310void OBJWriter::write_uv_coords(FormatHandler &fh, OBJMesh &r_obj_mesh_data) const
311{
312 const Span<float2> uv_coords = r_obj_mesh_data.get_uv_coords();
313 obj_parallel_chunked_output(fh, uv_coords.size(), [&](FormatHandler &buf, int i) {
314 const float2 &uv_vertex = uv_coords[i];
315 buf.write_obj_uv(uv_vertex[0], uv_vertex[1]);
316 });
317}
318
320{
321 /* Poly normals should be calculated earlier via store_normal_coords_and_indices. */
322 const Span<float3> normal_coords = obj_mesh_data.get_normal_coords();
323 obj_parallel_chunked_output(fh, normal_coords.size(), [&](FormatHandler &buf, int i) {
324 const float3 &normal = normal_coords[i];
325 buf.write_obj_normal(normal[0], normal[1], normal[2]);
326 });
327}
328
329OBJWriter::func_vert_uv_normal_indices OBJWriter::get_face_element_writer(
330 const int total_uv_vertices) const
331{
332 if (export_params_.export_normals) {
333 if (export_params_.export_uv && (total_uv_vertices > 0)) {
334 /* Write both normals and UV indices. */
335 return &OBJWriter::write_vert_uv_normal_indices;
336 }
337 /* Write normals indices. */
338 return &OBJWriter::write_vert_normal_indices;
339 }
340 /* Write UV indices. */
341 if (export_params_.export_uv && (total_uv_vertices > 0)) {
342 return &OBJWriter::write_vert_uv_indices;
343 }
344 /* Write neither normals nor UV indices. */
345 return &OBJWriter::write_vert_indices;
346}
347
348static int get_smooth_group(const OBJMesh &mesh, const OBJExportParams &params, int face_idx)
349{
350 if (face_idx < 0) {
351 return NEGATIVE_INIT;
352 }
353 int group = SMOOTH_GROUP_DISABLED;
354 if (mesh.is_ith_face_smooth(face_idx)) {
355 group = !params.export_smooth_groups ? SMOOTH_GROUP_DEFAULT : mesh.ith_smooth_group(face_idx);
356 }
357 return group;
358}
359
361 const IndexOffsets &offsets,
362 const OBJMesh &obj_mesh_data,
363 FunctionRef<const char *(int)> matname_fn)
364{
365 const func_vert_uv_normal_indices face_element_writer = get_face_element_writer(
366 obj_mesh_data.tot_uv_vertices());
367
368 const int tot_faces = obj_mesh_data.tot_faces();
369 const int tot_deform_groups = obj_mesh_data.tot_deform_groups();
371 const bke::AttributeAccessor attributes = obj_mesh_data.get_mesh()->attributes();
372 const VArray<int> material_indices = *attributes.lookup_or_default<int>(
373 "material_index", bke::AttrDomain::Face, 0);
374
375 obj_parallel_chunked_output(fh, tot_faces, [&](FormatHandler &buf, int idx) {
376 /* Polygon order for writing into the file is not necessarily the same
377 * as order in the mesh; it will be sorted by material indices. Remap current
378 * and previous indices here according to the order. */
379 int prev_i = obj_mesh_data.remap_face_index(idx - 1);
380 int i = obj_mesh_data.remap_face_index(idx);
381
382 const Span<int> face_vertex_indices = obj_mesh_data.calc_face_vert_indices(i);
383 const Span<int> face_uv_indices = obj_mesh_data.get_face_uv_indices(i);
384 const Span<int> face_normal_indices = obj_mesh_data.get_face_normal_indices(i);
385
386 /* Write smoothing group if different from previous. */
387 {
388 const int prev_group = get_smooth_group(obj_mesh_data, export_params_, prev_i);
389 const int group = get_smooth_group(obj_mesh_data, export_params_, i);
390 if (group != prev_group) {
391 buf.write_obj_smooth(group);
392 }
393 }
394
395 /* Write vertex group if different from previous. */
396 if (export_params_.export_vertex_groups) {
397 Vector<float> &local_weights = group_weights.local();
398 local_weights.resize(tot_deform_groups);
399 const int16_t prev_group = idx == 0 ? NEGATIVE_INIT :
400 obj_mesh_data.get_face_deform_group_index(
401 prev_i, local_weights);
402 const int16_t group = obj_mesh_data.get_face_deform_group_index(i, local_weights);
403 if (group != prev_group) {
405 obj_mesh_data.get_face_deform_group_name(group));
406 }
407 }
408
409 /* Write material name and material group if different from previous. */
410 if ((export_params_.export_materials || export_params_.export_material_groups) &&
411 obj_mesh_data.tot_materials() > 0)
412 {
413 const int16_t prev_mat = idx == 0 ? NEGATIVE_INIT : std::max(0, material_indices[prev_i]);
414 const int16_t mat = std::max(0, material_indices[i]);
415 if (mat != prev_mat) {
416 if (mat == NOT_FOUND) {
417 if (export_params_.export_materials) {
419 }
420 }
421 else {
422 const char *mat_name = matname_fn(mat);
423 if (!mat_name) {
424 mat_name = MATERIAL_GROUP_DISABLED;
425 }
426 if (export_params_.export_material_groups) {
427 std::string object_name = obj_mesh_data.get_object_name();
428 spaces_to_underscores(object_name);
429 buf.write_obj_group(object_name + "_" + mat_name);
430 }
431 if (export_params_.export_materials) {
432 buf.write_obj_usemtl(mat_name);
433 }
434 }
435 }
436 }
437
438 /* Write face elements. */
439 (this->*face_element_writer)(buf,
440 offsets,
441 face_vertex_indices,
442 face_uv_indices,
443 face_normal_indices,
444 obj_mesh_data.is_mirrored_transform());
445 });
446}
447
449 const IndexOffsets &offsets,
450 const OBJMesh &obj_mesh_data) const
451{
452 const Mesh &mesh = *obj_mesh_data.get_mesh();
453 const bke::LooseEdgeCache &loose_edges = mesh.loose_edges();
454 if (loose_edges.count == 0) {
455 return;
456 }
457
458 const Span<int2> edges = mesh.edges();
459 for (const int64_t i : edges.index_range()) {
460 if (loose_edges.is_loose_bits[i]) {
461 const int2 obj_edge = edges[i] + offsets.vertex_offset + 1;
462 fh.write_obj_edge(obj_edge[0], obj_edge[1]);
463 }
464 }
465}
466
467void OBJWriter::write_nurbs_curve(FormatHandler &fh, const OBJCurve &obj_nurbs_data) const
468{
469
470 const int total_splines = obj_nurbs_data.total_splines();
471 for (int spline_idx = 0; spline_idx < total_splines; spline_idx++) {
472 /* Double check no surface is passed in as they are no supported (this is filtered when parsed)
473 */
474 BLI_assert(obj_nurbs_data.get_spline(spline_idx)->pntsv == 1);
475
476 const int total_vertices = obj_nurbs_data.total_spline_vertices(spline_idx);
477 for (int vertex_idx = 0; vertex_idx < total_vertices; vertex_idx++) {
478 const float3 vertex_coords = obj_nurbs_data.vertex_coordinates(
479 spline_idx, vertex_idx, export_params_.global_scale);
480 fh.write_obj_vertex(vertex_coords[0], vertex_coords[1], vertex_coords[2]);
481 }
482
483 const char *nurbs_name = obj_nurbs_data.get_curve_name();
484 const int degree_u = obj_nurbs_data.get_nurbs_degree_u(spline_idx);
485 fh.write_obj_group(nurbs_name);
486 fh.write_obj_cstype();
487 fh.write_obj_nurbs_degree(degree_u);
495 const int num_points_u = obj_nurbs_data.num_control_points_u(spline_idx);
496 Vector<float> knot_buffer;
497 Span<float> knotsu = obj_nurbs_data.get_knots_u(spline_idx, knot_buffer);
498
500 fh.write_obj_nurbs_parm(knotsu[degree_u]);
501 fh.write_obj_nurbs_parm(knotsu.last(degree_u));
502
503 for (int i = 0; i < num_points_u; i++) {
504 /* "+1" to keep indices one-based, even if they're negative: i.e., -1 refers to the
505 * last vertex coordinate, -2 second last. */
506 fh.write_obj_face_v(-((i % total_vertices) + 1));
507 }
509
511 for (const float &u : knotsu) {
513 }
516 }
517}
518
519/* -------------------------------------------------------------------- */
522
523static const char *tex_map_type_to_string[] = {
524 "map_Kd",
525 "map_Pm",
526 "map_Ks",
527 "map_Ns",
528 "map_Pr",
529 "map_Ps",
530 "map_refl",
531 "map_Ke",
532 "map_d",
533 "map_Bump",
534};
536 "array size mismatch");
537
542static std::string float3_to_string(const float3 &numbers)
543{
544 return fmt::format("{} {} {}", numbers[0], numbers[1], numbers[2]);
545}
546
547MTLWriter::MTLWriter(const char *obj_filepath, bool write_file) noexcept(false)
548{
549 if (!write_file) {
550 return;
551 }
552 char mtl_path[FILE_MAX];
553 STRNCPY(mtl_path, obj_filepath);
554
555 const bool ok = BLI_path_extension_replace(mtl_path, sizeof(mtl_path), ".mtl");
556 if (!ok) {
557 throw std::system_error(ENAMETOOLONG, std::system_category(), "");
558 }
559
560 mtl_filepath_ = mtl_path;
561 outfile_ = BLI_fopen(mtl_filepath_.c_str(), "wb");
562 if (!outfile_) {
563 throw std::system_error(errno, std::system_category(), "Cannot open file " + mtl_filepath_);
564 }
565}
567{
568 if (outfile_) {
569 fmt_handler_.write_to_file(outfile_);
570 if (std::fclose(outfile_)) {
572 "Error: could not close file '%s' properly, it may be corrupted.",
573 mtl_filepath_.c_str());
574 }
575 }
576}
577
578void MTLWriter::write_header(const char *blen_filepath)
579{
580 using namespace std::string_literals;
581 const char *blen_basename = (blen_filepath && blen_filepath[0] != '\0') ?
582 BLI_path_basename(blen_filepath) :
583 "None";
584 fmt_handler_.write_string("# Blender "s + BKE_blender_version_string() + " MTL File: '" +
585 blen_basename + "'");
586 fmt_handler_.write_string("# www.blender.org");
587}
588
590{
591 return mtl_filepath_;
592}
593
594void MTLWriter::write_bsdf_properties(const MTLMaterial &mtl, bool write_pbr)
595{
596 /* For various material properties, we only capture information
597 * coming from the texture, or the default value of the socket.
598 * When the texture is present, do not emit the default value. */
599
600 /* Do not write Ns & Ka when writing in PBR mode. */
601 if (!write_pbr) {
603 fmt_handler_.write_mtl_float("Ns", mtl.spec_exponent);
604 }
605 fmt_handler_.write_mtl_float3(
606 "Ka", mtl.ambient_color.x, mtl.ambient_color.y, mtl.ambient_color.z);
607 }
609 fmt_handler_.write_mtl_float3("Kd", mtl.color.x, mtl.color.y, mtl.color.z);
610 }
612 fmt_handler_.write_mtl_float3("Ks", mtl.spec_color.x, mtl.spec_color.y, mtl.spec_color.z);
613 }
615 fmt_handler_.write_mtl_float3(
616 "Ke", mtl.emission_color.x, mtl.emission_color.y, mtl.emission_color.z);
617 }
618 fmt_handler_.write_mtl_float("Ni", mtl.ior);
620 fmt_handler_.write_mtl_float("d", mtl.alpha);
621 }
622 fmt_handler_.write_mtl_illum(mtl.illum_mode);
623
624 if (write_pbr) {
625 if (!mtl.tex_map_of_type(MTLTexMapType::Roughness).is_valid() && mtl.roughness >= 0.0f) {
626 fmt_handler_.write_mtl_float("Pr", mtl.roughness);
627 }
628 if (!mtl.tex_map_of_type(MTLTexMapType::Metallic).is_valid() && mtl.metallic >= 0.0f) {
629 fmt_handler_.write_mtl_float("Pm", mtl.metallic);
630 }
631 if (!mtl.tex_map_of_type(MTLTexMapType::Sheen).is_valid() && mtl.sheen >= 0.0f) {
632 fmt_handler_.write_mtl_float("Ps", mtl.sheen);
633 }
634 if (mtl.cc_thickness >= 0.0f) {
635 fmt_handler_.write_mtl_float("Pc", mtl.cc_thickness);
636 }
637 if (mtl.cc_roughness >= 0.0f) {
638 fmt_handler_.write_mtl_float("Pcr", mtl.cc_roughness);
639 }
640 if (mtl.aniso >= 0.0f) {
641 fmt_handler_.write_mtl_float("aniso", mtl.aniso);
642 }
643 if (mtl.aniso_rot >= 0.0f) {
644 fmt_handler_.write_mtl_float("anisor", mtl.aniso_rot);
645 }
646 if (mtl.transmit_color.x > 0.0f || mtl.transmit_color.y > 0.0f || mtl.transmit_color.z > 0.0f)
647 {
648 fmt_handler_.write_mtl_float3(
649 "Tf", mtl.transmit_color.x, mtl.transmit_color.y, mtl.transmit_color.z);
650 }
651 }
652}
653
654void MTLWriter::write_texture_map(const MTLMaterial &mtl_material,
655 MTLTexMapType texture_key,
656 const MTLTexMap &texture_map,
657 const char *blen_filedir,
658 const char *dest_dir,
659 ePathReferenceMode path_mode,
660 Set<std::pair<std::string, std::string>> &copy_set)
661{
662 std::string options;
663 /* Option strings should have their own leading spaces. */
664 if (texture_map.translation != float3{0.0f, 0.0f, 0.0f}) {
665 options.append(" -o ").append(float3_to_string(texture_map.translation));
666 }
667 if (texture_map.scale != float3{1.0f, 1.0f, 1.0f}) {
668 options.append(" -s ").append(float3_to_string(texture_map.scale));
669 }
670 if (texture_key == MTLTexMapType::Normal && mtl_material.normal_strength > 0.0001f) {
671 options.append(" -bm ").append(std::to_string(mtl_material.normal_strength));
672 }
673
674 std::string path = path_reference(
675 texture_map.image_path.c_str(), blen_filedir, dest_dir, path_mode, &copy_set);
676 /* Always emit forward slashes for cross-platform compatibility. */
677 std::replace(path.begin(), path.end(), '\\', '/');
678
679 fmt_handler_.write_mtl_map(tex_map_type_to_string[int(texture_key)], options, path);
680}
681
682static bool is_pbr_map(MTLTexMapType type)
683{
684 return type == MTLTexMapType::Metallic || type == MTLTexMapType::Roughness ||
685 type == MTLTexMapType::Sheen;
686}
687
689{
691}
692
693void MTLWriter::write_materials(const char *blen_filepath,
694 ePathReferenceMode path_mode,
695 const char *dest_dir,
696 bool write_pbr)
697{
698 if (mtlmaterials_.is_empty()) {
699 return;
700 }
701
702 char blen_filedir[FILE_MAX];
703 BLI_path_split_dir_part(blen_filepath, blen_filedir, sizeof(blen_filedir));
704 BLI_path_slash_native(blen_filedir);
705 BLI_path_normalize(blen_filedir);
706
707 std::sort(mtlmaterials_.begin(),
708 mtlmaterials_.end(),
709 [](const MTLMaterial &a, const MTLMaterial &b) { return a.name < b.name; });
711 for (const MTLMaterial &mtlmat : mtlmaterials_) {
712 fmt_handler_.write_string("");
713 fmt_handler_.write_mtl_newmtl(mtlmat.name);
714 write_bsdf_properties(mtlmat, write_pbr);
715 for (int key = 0; key < int(MTLTexMapType::Count); key++) {
716 const MTLTexMap &tex = mtlmat.texture_maps[key];
717 if (!tex.is_valid()) {
718 continue;
719 }
720 if (!write_pbr && is_pbr_map((MTLTexMapType)key)) {
721 continue;
722 }
723 if (write_pbr && is_non_pbr_map((MTLTexMapType)key)) {
724 continue;
725 }
726 write_texture_map(
727 mtlmat, (MTLTexMapType)key, tex, blen_filedir, dest_dir, path_mode, copy_set);
728 }
729 }
730 path_reference_copy(copy_set);
731}
732
734{
735 Vector<int> r_mtl_indices;
736 r_mtl_indices.resize(mesh_to_export.tot_materials());
737 for (int16_t i = 0; i < mesh_to_export.tot_materials(); i++) {
738 const Material *material = mesh_to_export.materials[i];
739 if (!material) {
740 r_mtl_indices[i] = -1;
741 continue;
742 }
743 int mtlmat_index = material_map_.lookup_default(material, -1);
744 if (mtlmat_index != -1) {
745 r_mtl_indices[i] = mtlmat_index;
746 }
747 else {
748 mtlmaterials_.append(mtlmaterial_for_material(material));
749 r_mtl_indices[i] = mtlmaterials_.size() - 1;
750 material_map_.add_new(material, r_mtl_indices[i]);
751 }
752 }
753 return r_mtl_indices;
754}
755
756const char *MTLWriter::mtlmaterial_name(int index)
757{
758 if (index < 0 || index >= mtlmaterials_.size()) {
759 return nullptr;
760 }
761 return mtlmaterials_[index].name.c_str();
762}
763
764
765} // namespace blender::io::obj
const char * BKE_blender_version_string(void)
Definition blender.cc:143
#define BLI_STATIC_ASSERT(a, msg)
Definition BLI_assert.h:83
#define BLI_assert(a)
Definition BLI_assert.h:46
File and directory operations.
FILE * BLI_fopen(const char *filepath, const char *mode) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
void linearrgb_to_srgb_v3_v3(float srgb[3], const float linear[3])
void void void const char * BLI_path_basename(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT
#define FILE_MAXFILE
#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 BLI_path_split_dir_file(const char *filepath, char *dir, size_t dir_maxncpy, char *file, size_t file_maxncpy) ATTR_NONNULL(1
void void BLI_path_split_dir_part(const char *filepath, char *dir, size_t dir_maxncpy) ATTR_NONNULL(1
#define FILE_MAXDIR
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:688
#define ARRAY_SIZE(arr)
#define CLOG_ERROR(clg_ref,...)
Definition CLG_log.h:182
ePathReferenceMode
SIMD_FORCE_INLINE btVector3 transform(const btVector3 &point) const
long long int int64_t
AttributeSet attributes
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr const T & last(const int64_t n=0) const
Definition BLI_span.hh:325
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
constexpr bool is_empty() const
constexpr const char * data() const
T get(const int64_t index) const
int64_t size() const
void resize(const int64_t new_size)
GAttributeReader lookup_or_default(StringRef attribute_id, AttrDomain domain, eCustomDataType data_type, const void *default_value=nullptr) const
void write_obj_face_v_uv_normal(int v, int uv, int n)
void write_mtl_float3(const char *type, float r, float g, float b)
void write_obj_vertex(float x, float y, float z)
void write_obj_vertex_color(float x, float y, float z, float r, float g, float b)
void write_mtl_float(const char *type, float v)
void append_from(FormatHandler &v)
MTLWriter(const char *obj_filepath, bool write_file) noexcept(false)
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)
int total_spline_vertices(int spline_index) const
const Nurb * get_spline(int spline_index) const
float3 vertex_coordinates(int spline_index, int vertex_index, float global_scale) const
int get_nurbs_degree_u(int spline_index) const
int num_control_points_u(int spline_index) const
const char * get_curve_name() const
Span< float > get_knots_u(int spline_index, Vector< float > &buffer) const
const char * get_face_deform_group_name(int16_t def_group_index) const
int16_t get_face_deform_group_index(int face_index, MutableSpan< float > group_weights) const
bool is_ith_face_smooth(int face_index) const
StringRef get_object_mesh_name() const
Span< int > get_face_uv_indices(const int face_index) const
int remap_face_index(int i) const
Array< const Material * > materials
Span< float3 > get_normal_coords() const
const float4x4 & get_world_axes_transform() const
StringRef get_object_name() const
Span< int > get_face_normal_indices(const int face_index) const
Span< int > calc_face_vert_indices(const int face_index) const
Span< float2 > get_uv_coords() const
int ith_smooth_group(int face_index) const
const Mesh * get_mesh() const
void write_normals(FormatHandler &fh, OBJMesh &obj_mesh_data)
OBJWriter(const char *filepath, const OBJExportParams &export_params) noexcept(false)
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 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
CCL_NAMESPACE_BEGIN struct Options options
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
int count
#define LOG(severity)
Definition log.h:32
static void spaces_to_underscores(std::string &r_name)
static bool is_pbr_map(MTLTexMapType type)
static const char * tex_map_type_to_string[]
static const char * MATERIAL_GROUP_DISABLED
MTLMaterial mtlmaterial_for_material(const Material *material)
static int get_smooth_group(const OBJMesh &mesh, const OBJExportParams &params, int face_idx)
void obj_parallel_chunked_output(FormatHandler &fh, int tot_count, const Function &function)
static bool is_non_pbr_map(MTLTexMapType type)
static std::string float3_to_string(const float3 &numbers)
static const int chunk_size
static int calc_chunk_count(int count)
static const char * DEFORM_GROUP_DISABLED
std::string path_reference(StringRefNull filepath, StringRefNull base_src, StringRefNull base_dst, ePathReferenceMode mode, Set< std::pair< std::string, std::string > > *copy_set)
Definition path_util.cc:15
void path_reference_copy(const Set< std::pair< std::string, std::string > > &copy_set)
Definition path_util.cc:64
VecBase< T, 3 > transform_point(const CartesianBasis &basis, const VecBase< T, 3 > &v)
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
MatBase< float, 4, 4 > float4x4
VecBase< int32_t, 2 > int2
ColorSceneLinear4f< eAlpha::Premultiplied > ColorGeometry4f
Definition BLI_color.hh:342
VecBase< float, 3 > float3
char * active_color_attribute
blender::BitVector is_loose_bits
const MTLTexMap & tex_map_of_type(MTLTexMapType key) const
i
Definition text_draw.cc:230
char * buffers[2]