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