Blender V4.3
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 <sstream>
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_math_matrix.hh"
19#include "BLI_path_utils.hh"
20#include "BLI_string.h"
21#include "BLI_task.hh"
22
23#include "IO_path_util.hh"
24
25#include "obj_export_mesh.hh"
26#include "obj_export_mtl.hh"
27#include "obj_export_nurbs.hh"
28
30
41
42static const char *DEFORM_GROUP_DISABLED = "off";
43/* There is no deform group default name. Use what the user set in the UI. */
44
50static const char *MATERIAL_GROUP_DISABLED = "";
51
52void OBJWriter::write_vert_uv_normal_indices(FormatHandler &fh,
53 const IndexOffsets &offsets,
54 Span<int> vert_indices,
55 Span<int> uv_indices,
56 Span<int> normal_indices,
57 bool flip) const
58{
59 BLI_assert(vert_indices.size() == uv_indices.size() &&
60 vert_indices.size() == normal_indices.size());
61 const int vertex_offset = offsets.vertex_offset + 1;
62 const int uv_offset = offsets.uv_vertex_offset + 1;
63 const int normal_offset = offsets.normal_offset + 1;
64 const int n = vert_indices.size();
66 if (!flip) {
67 for (int j = 0; j < n; ++j) {
68 fh.write_obj_face_v_uv_normal(vert_indices[j] + vertex_offset,
69 uv_indices[j] + uv_offset,
70 normal_indices[j] + normal_offset);
71 }
72 }
73 else {
74 /* For a transform that is mirrored (negative scale on odd number of axes),
75 * we want to flip the face index order. Start from the same index, and
76 * then go backwards. Same logic in other write_*_indices functions below. */
77 for (int k = 0; k < n; ++k) {
78 int j = k == 0 ? 0 : n - k;
79 fh.write_obj_face_v_uv_normal(vert_indices[j] + vertex_offset,
80 uv_indices[j] + uv_offset,
81 normal_indices[j] + normal_offset);
82 }
83 }
85}
86
87void OBJWriter::write_vert_normal_indices(FormatHandler &fh,
88 const IndexOffsets &offsets,
89 Span<int> vert_indices,
90 Span<int> /*uv_indices*/,
91 Span<int> normal_indices,
92 bool flip) const
93{
94 BLI_assert(vert_indices.size() == normal_indices.size());
95 const int vertex_offset = offsets.vertex_offset + 1;
96 const int normal_offset = offsets.normal_offset + 1;
97 const int n = vert_indices.size();
98 fh.write_obj_face_begin();
99 if (!flip) {
100 for (int j = 0; j < n; ++j) {
101 fh.write_obj_face_v_normal(vert_indices[j] + vertex_offset,
102 normal_indices[j] + normal_offset);
103 }
104 }
105 else {
106 for (int k = 0; k < n; ++k) {
107 int j = k == 0 ? 0 : n - k;
108 fh.write_obj_face_v_normal(vert_indices[j] + vertex_offset,
109 normal_indices[j] + normal_offset);
110 }
111 }
112 fh.write_obj_face_end();
113}
114
115void OBJWriter::write_vert_uv_indices(FormatHandler &fh,
116 const IndexOffsets &offsets,
117 Span<int> vert_indices,
118 Span<int> uv_indices,
119 Span<int> /*normal_indices*/,
120 bool flip) const
121{
122 BLI_assert(vert_indices.size() == uv_indices.size());
123 const int vertex_offset = offsets.vertex_offset + 1;
124 const int uv_offset = offsets.uv_vertex_offset + 1;
125 const int n = vert_indices.size();
126 fh.write_obj_face_begin();
127 if (!flip) {
128 for (int j = 0; j < n; ++j) {
129 fh.write_obj_face_v_uv(vert_indices[j] + vertex_offset, uv_indices[j] + uv_offset);
130 }
131 }
132 else {
133 for (int k = 0; k < n; ++k) {
134 int j = k == 0 ? 0 : n - k;
135 fh.write_obj_face_v_uv(vert_indices[j] + vertex_offset, uv_indices[j] + uv_offset);
136 }
137 }
138 fh.write_obj_face_end();
139}
140
141void OBJWriter::write_vert_indices(FormatHandler &fh,
142 const IndexOffsets &offsets,
143 Span<int> vert_indices,
144 Span<int> /*uv_indices*/,
145 Span<int> /*normal_indices*/,
146 bool flip) const
147{
148 const int vertex_offset = offsets.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(vert_indices[j] + vertex_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(vert_indices[j] + vertex_offset);
160 }
161 }
162 fh.write_obj_face_end();
163}
164
166{
167 using namespace std::string_literals;
168 FormatHandler fh;
169 fh.write_string("# Blender "s + BKE_blender_version_string());
170 fh.write_string("# www.blender.org");
171 fh.write_to_file(outfile_);
172}
173
174void OBJWriter::write_mtllib_name(const StringRefNull mtl_filepath) const
175{
176 /* Split `.MTL` file path into parent directory and filename. */
177 char mtl_file_name[FILE_MAXFILE];
178 char mtl_dir_name[FILE_MAXDIR];
179 BLI_path_split_dir_file(mtl_filepath.data(),
180 mtl_dir_name,
181 sizeof(mtl_dir_name),
182 mtl_file_name,
183 sizeof(mtl_file_name));
184 FormatHandler fh;
185 fh.write_obj_mtllib(mtl_file_name);
186 fh.write_to_file(outfile_);
187}
188
189static void spaces_to_underscores(std::string &r_name)
190{
191 std::replace(r_name.begin(), r_name.end(), ' ', '_');
192}
193
194void OBJWriter::write_object_name(FormatHandler &fh, const OBJMesh &obj_mesh_data) const
195{
196 std::string object_name = obj_mesh_data.get_object_name();
197 spaces_to_underscores(object_name);
198 if (export_params_.export_object_groups) {
199 std::string mesh_name = obj_mesh_data.get_object_mesh_name();
200 spaces_to_underscores(mesh_name);
201 fh.write_obj_group(object_name + "_" + mesh_name);
202 return;
203 }
204 fh.write_obj_object(object_name);
205}
206
207/* Split up large meshes into multi-threaded jobs; each job processes
208 * this amount of items. */
209static const int chunk_size = 32768;
210static int calc_chunk_count(int count)
211{
212 return (count + chunk_size - 1) / chunk_size;
213}
214
215/* Write /tot_count/ items to OBJ file output. Each item is written
216 * by a /function/ that should be independent from other items.
217 * If the amount of items is large enough (> chunk_size), then writing
218 * will be done in parallel, into temporary FormatHandler buffers that
219 * will be written into the final /fh/ buffer at the end.
220 */
221template<typename Function>
222void obj_parallel_chunked_output(FormatHandler &fh, int tot_count, const Function &function)
223{
224 if (tot_count <= 0) {
225 return;
226 }
227 /* If we have just one chunk, process it directly into the output
228 * buffer - avoids all the job scheduling and temporary vector allocation
229 * overhead. */
230 const int chunk_count = calc_chunk_count(tot_count);
231 if (chunk_count == 1) {
232 for (int i = 0; i < tot_count; i++) {
233 function(fh, i);
234 }
235 return;
236 }
237 /* Give each chunk its own temporary output buffer, and process them in parallel. */
238 Array<FormatHandler> buffers(chunk_count);
239 threading::parallel_for(IndexRange(chunk_count), 1, [&](IndexRange range) {
240 for (const int r : range) {
241 int i_start = r * chunk_size;
242 int i_end = std::min(i_start + chunk_size, tot_count);
243 auto &buf = buffers[r];
244 for (int i = i_start; i < i_end; i++) {
245 function(buf, i);
246 }
247 }
248 });
249 /* Emit all temporary output buffers into the destination buffer. */
250 for (auto &buf : buffers) {
251 fh.append_from(buf);
252 }
253}
254
256 const OBJMesh &obj_mesh_data,
257 bool write_colors) const
258{
259 const int tot_count = obj_mesh_data.tot_vertices();
260
261 const Mesh *mesh = obj_mesh_data.get_mesh();
262 const StringRef name = mesh->active_color_attribute;
263
264 const float4x4 transform = obj_mesh_data.get_world_axes_transform();
265 const Span<float3> positions = obj_mesh_data.get_mesh()->vert_positions();
266
267 if (write_colors && !name.is_empty()) {
268 const bke::AttributeAccessor attributes = mesh->attributes();
270 name, bke::AttrDomain::Point, {0.0f, 0.0f, 0.0f, 0.0f});
271
272 BLI_assert(tot_count == attribute.size());
273 obj_parallel_chunked_output(fh, tot_count, [&](FormatHandler &buf, int i) {
274 const float3 vertex = math::transform_point(transform, positions[i]);
275 ColorGeometry4f linear = attribute.get(i);
276 float srgb[3];
277 linearrgb_to_srgb_v3_v3(srgb, linear);
278 buf.write_obj_vertex_color(vertex[0], vertex[1], vertex[2], srgb[0], srgb[1], srgb[2]);
279 });
280 }
281 else {
282 obj_parallel_chunked_output(fh, tot_count, [&](FormatHandler &buf, int i) {
283 const float3 vertex = math::transform_point(transform, positions[i]);
284 buf.write_obj_vertex(vertex[0], vertex[1], vertex[2]);
285 });
286 }
287}
288
289void OBJWriter::write_uv_coords(FormatHandler &fh, OBJMesh &r_obj_mesh_data) const
290{
291 const Span<float2> uv_coords = r_obj_mesh_data.get_uv_coords();
292 obj_parallel_chunked_output(fh, uv_coords.size(), [&](FormatHandler &buf, int i) {
293 const float2 &uv_vertex = uv_coords[i];
294 buf.write_obj_uv(uv_vertex[0], uv_vertex[1]);
295 });
296}
297
299{
300 /* Poly normals should be calculated earlier via store_normal_coords_and_indices. */
301 const Span<float3> normal_coords = obj_mesh_data.get_normal_coords();
302 obj_parallel_chunked_output(fh, normal_coords.size(), [&](FormatHandler &buf, int i) {
303 const float3 &normal = normal_coords[i];
304 buf.write_obj_normal(normal[0], normal[1], normal[2]);
305 });
306}
307
308OBJWriter::func_vert_uv_normal_indices OBJWriter::get_face_element_writer(
309 const int total_uv_vertices) const
310{
311 if (export_params_.export_normals) {
312 if (export_params_.export_uv && (total_uv_vertices > 0)) {
313 /* Write both normals and UV indices. */
314 return &OBJWriter::write_vert_uv_normal_indices;
315 }
316 /* Write normals indices. */
317 return &OBJWriter::write_vert_normal_indices;
318 }
319 /* Write UV indices. */
320 if (export_params_.export_uv && (total_uv_vertices > 0)) {
321 return &OBJWriter::write_vert_uv_indices;
322 }
323 /* Write neither normals nor UV indices. */
324 return &OBJWriter::write_vert_indices;
325}
326
327static int get_smooth_group(const OBJMesh &mesh, const OBJExportParams &params, int face_idx)
328{
329 if (face_idx < 0) {
330 return NEGATIVE_INIT;
331 }
332 int group = SMOOTH_GROUP_DISABLED;
333 if (mesh.is_ith_face_smooth(face_idx)) {
334 group = !params.export_smooth_groups ? SMOOTH_GROUP_DEFAULT : mesh.ith_smooth_group(face_idx);
335 }
336 return group;
337}
338
340 const IndexOffsets &offsets,
341 const OBJMesh &obj_mesh_data,
342 FunctionRef<const char *(int)> matname_fn)
343{
344 const func_vert_uv_normal_indices face_element_writer = get_face_element_writer(
345 obj_mesh_data.tot_uv_vertices());
346
347 const int tot_faces = obj_mesh_data.tot_faces();
348 const int tot_deform_groups = obj_mesh_data.tot_deform_groups();
350 const bke::AttributeAccessor attributes = obj_mesh_data.get_mesh()->attributes();
351 const VArray<int> material_indices = *attributes.lookup_or_default<int>(
352 "material_index", bke::AttrDomain::Face, 0);
353
354 obj_parallel_chunked_output(fh, tot_faces, [&](FormatHandler &buf, int idx) {
355 /* Polygon order for writing into the file is not necessarily the same
356 * as order in the mesh; it will be sorted by material indices. Remap current
357 * and previous indices here according to the order. */
358 int prev_i = obj_mesh_data.remap_face_index(idx - 1);
359 int i = obj_mesh_data.remap_face_index(idx);
360
361 const Span<int> face_vertex_indices = obj_mesh_data.calc_face_vert_indices(i);
362 const Span<int> face_uv_indices = obj_mesh_data.get_face_uv_indices(i);
363 const Span<int> face_normal_indices = obj_mesh_data.get_face_normal_indices(i);
364
365 /* Write smoothing group if different from previous. */
366 {
367 const int prev_group = get_smooth_group(obj_mesh_data, export_params_, prev_i);
368 const int group = get_smooth_group(obj_mesh_data, export_params_, i);
369 if (group != prev_group) {
370 buf.write_obj_smooth(group);
371 }
372 }
373
374 /* Write vertex group if different from previous. */
375 if (export_params_.export_vertex_groups) {
376 Vector<float> &local_weights = group_weights.local();
377 local_weights.resize(tot_deform_groups);
378 const int16_t prev_group = idx == 0 ? NEGATIVE_INIT :
379 obj_mesh_data.get_face_deform_group_index(
380 prev_i, local_weights);
381 const int16_t group = obj_mesh_data.get_face_deform_group_index(i, local_weights);
382 if (group != prev_group) {
384 obj_mesh_data.get_face_deform_group_name(group));
385 }
386 }
387
388 /* Write material name and material group if different from previous. */
389 if ((export_params_.export_materials || export_params_.export_material_groups) &&
390 obj_mesh_data.tot_materials() > 0)
391 {
392 const int16_t prev_mat = idx == 0 ? NEGATIVE_INIT : std::max(0, material_indices[prev_i]);
393 const int16_t mat = std::max(0, material_indices[i]);
394 if (mat != prev_mat) {
395 if (mat == NOT_FOUND) {
396 if (export_params_.export_materials) {
398 }
399 }
400 else {
401 const char *mat_name = matname_fn(mat);
402 if (!mat_name) {
403 mat_name = MATERIAL_GROUP_DISABLED;
404 }
405 if (export_params_.export_material_groups) {
406 std::string object_name = obj_mesh_data.get_object_name();
407 spaces_to_underscores(object_name);
408 buf.write_obj_group(object_name + "_" + mat_name);
409 }
410 if (export_params_.export_materials) {
411 buf.write_obj_usemtl(mat_name);
412 }
413 }
414 }
415 }
416
417 /* Write face elements. */
418 (this->*face_element_writer)(buf,
419 offsets,
420 face_vertex_indices,
421 face_uv_indices,
422 face_normal_indices,
423 obj_mesh_data.is_mirrored_transform());
424 });
425}
426
428 const IndexOffsets &offsets,
429 const OBJMesh &obj_mesh_data) const
430{
431 const Mesh &mesh = *obj_mesh_data.get_mesh();
432 const bke::LooseEdgeCache &loose_edges = mesh.loose_edges();
433 if (loose_edges.count == 0) {
434 return;
435 }
436
437 const Span<int2> edges = mesh.edges();
438 for (const int64_t i : edges.index_range()) {
439 if (loose_edges.is_loose_bits[i]) {
440 const int2 obj_edge = edges[i] + offsets.vertex_offset + 1;
441 fh.write_obj_edge(obj_edge[0], obj_edge[1]);
442 }
443 }
444}
445
446void OBJWriter::write_nurbs_curve(FormatHandler &fh, const OBJCurve &obj_nurbs_data) const
447{
448 const int total_splines = obj_nurbs_data.total_splines();
449 for (int spline_idx = 0; spline_idx < total_splines; spline_idx++) {
450 const int total_vertices = obj_nurbs_data.total_spline_vertices(spline_idx);
451 for (int vertex_idx = 0; vertex_idx < total_vertices; vertex_idx++) {
452 const float3 vertex_coords = obj_nurbs_data.vertex_coordinates(
453 spline_idx, vertex_idx, export_params_.global_scale);
454 fh.write_obj_vertex(vertex_coords[0], vertex_coords[1], vertex_coords[2]);
455 }
456
457 const char *nurbs_name = obj_nurbs_data.get_curve_name();
458 const int nurbs_degree = obj_nurbs_data.get_nurbs_degree(spline_idx);
459 fh.write_obj_group(nurbs_name);
460 fh.write_obj_cstype();
461 fh.write_obj_nurbs_degree(nurbs_degree);
469 const int total_control_points = obj_nurbs_data.total_spline_control_points(spline_idx);
471 for (int i = 0; i < total_control_points; i++) {
472 /* "+1" to keep indices one-based, even if they're negative: i.e., -1 refers to the
473 * last vertex coordinate, -2 second last. */
474 fh.write_obj_face_v(-((i % total_vertices) + 1));
475 }
477
483
484 const short flagsu = obj_nurbs_data.get_nurbs_flagu(spline_idx);
485 const bool cyclic = flagsu & CU_NURB_CYCLIC;
486 const bool endpoint = !cyclic && (flagsu & CU_NURB_ENDPOINT);
488 for (int i = 1; i <= total_control_points + 2; i++) {
489 float parm = 1.0f * i / (total_control_points + 2 + 1);
490 if (endpoint) {
491 if (i <= nurbs_degree) {
492 parm = 0;
493 }
494 else if (i > total_control_points + 2 - nurbs_degree) {
495 parm = 1;
496 }
497 }
498 fh.write_obj_nurbs_parm(parm);
499 }
502 }
503}
504
505/* -------------------------------------------------------------------- */
508
509static const char *tex_map_type_to_string[] = {
510 "map_Kd",
511 "map_Pm",
512 "map_Ks",
513 "map_Ns",
514 "map_Pr",
515 "map_Ps",
516 "map_refl",
517 "map_Ke",
518 "map_d",
519 "map_Bump",
520};
522 "array size mismatch");
523
528static std::string float3_to_string(const float3 &numbers)
529{
530 std::ostringstream r_string;
531 r_string << numbers[0] << " " << numbers[1] << " " << numbers[2];
532 return r_string.str();
533};
534
535MTLWriter::MTLWriter(const char *obj_filepath, bool write_file) noexcept(false)
536{
537 if (!write_file) {
538 return;
539 }
540 char mtl_path[FILE_MAX];
541 STRNCPY(mtl_path, obj_filepath);
542
543 const bool ok = BLI_path_extension_replace(mtl_path, sizeof(mtl_path), ".mtl");
544 if (!ok) {
545 throw std::system_error(ENAMETOOLONG, std::system_category(), "");
546 }
547
548 mtl_filepath_ = mtl_path;
549 outfile_ = BLI_fopen(mtl_filepath_.c_str(), "wb");
550 if (!outfile_) {
551 throw std::system_error(errno, std::system_category(), "Cannot open file " + mtl_filepath_);
552 }
553}
555{
556 if (outfile_) {
557 fmt_handler_.write_to_file(outfile_);
558 if (std::fclose(outfile_)) {
559 std::cerr << "Error: could not close the file '" << mtl_filepath_
560 << "' properly, it may be corrupted." << std::endl;
561 }
562 }
563}
564
565void MTLWriter::write_header(const char *blen_filepath)
566{
567 using namespace std::string_literals;
568 const char *blen_basename = (blen_filepath && blen_filepath[0] != '\0') ?
569 BLI_path_basename(blen_filepath) :
570 "None";
571 fmt_handler_.write_string("# Blender "s + BKE_blender_version_string() + " MTL File: '" +
572 blen_basename + "'");
573 fmt_handler_.write_string("# www.blender.org");
574}
575
577{
578 return mtl_filepath_;
579}
580
581void MTLWriter::write_bsdf_properties(const MTLMaterial &mtl, bool write_pbr)
582{
583 /* For various material properties, we only capture information
584 * coming from the texture, or the default value of the socket.
585 * When the texture is present, do not emit the default value. */
586
587 /* Do not write Ns & Ka when writing in PBR mode. */
588 if (!write_pbr) {
590 fmt_handler_.write_mtl_float("Ns", mtl.spec_exponent);
591 }
592 fmt_handler_.write_mtl_float3(
593 "Ka", mtl.ambient_color.x, mtl.ambient_color.y, mtl.ambient_color.z);
594 }
596 fmt_handler_.write_mtl_float3("Kd", mtl.color.x, mtl.color.y, mtl.color.z);
597 }
599 fmt_handler_.write_mtl_float3("Ks", mtl.spec_color.x, mtl.spec_color.y, mtl.spec_color.z);
600 }
602 fmt_handler_.write_mtl_float3(
603 "Ke", mtl.emission_color.x, mtl.emission_color.y, mtl.emission_color.z);
604 }
605 fmt_handler_.write_mtl_float("Ni", mtl.ior);
607 fmt_handler_.write_mtl_float("d", mtl.alpha);
608 }
609 fmt_handler_.write_mtl_illum(mtl.illum_mode);
610
611 if (write_pbr) {
612 if (!mtl.tex_map_of_type(MTLTexMapType::Roughness).is_valid() && mtl.roughness >= 0.0f) {
613 fmt_handler_.write_mtl_float("Pr", mtl.roughness);
614 }
615 if (!mtl.tex_map_of_type(MTLTexMapType::Metallic).is_valid() && mtl.metallic >= 0.0f) {
616 fmt_handler_.write_mtl_float("Pm", mtl.metallic);
617 }
618 if (!mtl.tex_map_of_type(MTLTexMapType::Sheen).is_valid() && mtl.sheen >= 0.0f) {
619 fmt_handler_.write_mtl_float("Ps", mtl.sheen);
620 }
621 if (mtl.cc_thickness >= 0.0f) {
622 fmt_handler_.write_mtl_float("Pc", mtl.cc_thickness);
623 }
624 if (mtl.cc_roughness >= 0.0f) {
625 fmt_handler_.write_mtl_float("Pcr", mtl.cc_roughness);
626 }
627 if (mtl.aniso >= 0.0f) {
628 fmt_handler_.write_mtl_float("aniso", mtl.aniso);
629 }
630 if (mtl.aniso_rot >= 0.0f) {
631 fmt_handler_.write_mtl_float("anisor", mtl.aniso_rot);
632 }
633 if (mtl.transmit_color.x > 0.0f || mtl.transmit_color.y > 0.0f || mtl.transmit_color.z > 0.0f)
634 {
635 fmt_handler_.write_mtl_float3(
636 "Tf", mtl.transmit_color.x, mtl.transmit_color.y, mtl.transmit_color.z);
637 }
638 }
639}
640
641void MTLWriter::write_texture_map(const MTLMaterial &mtl_material,
642 MTLTexMapType texture_key,
643 const MTLTexMap &texture_map,
644 const char *blen_filedir,
645 const char *dest_dir,
646 ePathReferenceMode path_mode,
647 Set<std::pair<std::string, std::string>> &copy_set)
648{
649 std::string options;
650 /* Option strings should have their own leading spaces. */
651 if (texture_map.translation != float3{0.0f, 0.0f, 0.0f}) {
652 options.append(" -o ").append(float3_to_string(texture_map.translation));
653 }
654 if (texture_map.scale != float3{1.0f, 1.0f, 1.0f}) {
655 options.append(" -s ").append(float3_to_string(texture_map.scale));
656 }
657 if (texture_key == MTLTexMapType::Normal && mtl_material.normal_strength > 0.0001f) {
658 options.append(" -bm ").append(std::to_string(mtl_material.normal_strength));
659 }
660
661 std::string path = path_reference(
662 texture_map.image_path.c_str(), blen_filedir, dest_dir, path_mode, &copy_set);
663 /* Always emit forward slashes for cross-platform compatibility. */
664 std::replace(path.begin(), path.end(), '\\', '/');
665
666 fmt_handler_.write_mtl_map(tex_map_type_to_string[int(texture_key)], options, path);
667}
668
669static bool is_pbr_map(MTLTexMapType type)
670{
671 return type == MTLTexMapType::Metallic || type == MTLTexMapType::Roughness ||
672 type == MTLTexMapType::Sheen;
673}
674
676{
678}
679
680void MTLWriter::write_materials(const char *blen_filepath,
681 ePathReferenceMode path_mode,
682 const char *dest_dir,
683 bool write_pbr)
684{
685 if (mtlmaterials_.is_empty()) {
686 return;
687 }
688
689 char blen_filedir[FILE_MAX];
690 BLI_path_split_dir_part(blen_filepath, blen_filedir, sizeof(blen_filedir));
691 BLI_path_slash_native(blen_filedir);
692 BLI_path_normalize(blen_filedir);
693
694 std::sort(mtlmaterials_.begin(),
695 mtlmaterials_.end(),
696 [](const MTLMaterial &a, const MTLMaterial &b) { return a.name < b.name; });
698 for (const MTLMaterial &mtlmat : mtlmaterials_) {
699 fmt_handler_.write_string("");
700 fmt_handler_.write_mtl_newmtl(mtlmat.name);
701 write_bsdf_properties(mtlmat, write_pbr);
702 for (int key = 0; key < int(MTLTexMapType::Count); key++) {
703 const MTLTexMap &tex = mtlmat.texture_maps[key];
704 if (!tex.is_valid()) {
705 continue;
706 }
707 if (!write_pbr && is_pbr_map((MTLTexMapType)key)) {
708 continue;
709 }
710 if (write_pbr && is_non_pbr_map((MTLTexMapType)key)) {
711 continue;
712 }
713 write_texture_map(
714 mtlmat, (MTLTexMapType)key, tex, blen_filedir, dest_dir, path_mode, copy_set);
715 }
716 }
717 path_reference_copy(copy_set);
718}
719
721{
722 Vector<int> r_mtl_indices;
723 r_mtl_indices.resize(mesh_to_export.tot_materials());
724 for (int16_t i = 0; i < mesh_to_export.tot_materials(); i++) {
725 const Material *material = mesh_to_export.materials[i];
726 if (!material) {
727 r_mtl_indices[i] = -1;
728 continue;
729 }
730 int mtlmat_index = material_map_.lookup_default(material, -1);
731 if (mtlmat_index != -1) {
732 r_mtl_indices[i] = mtlmat_index;
733 }
734 else {
735 mtlmaterials_.append(mtlmaterial_for_material(material));
736 r_mtl_indices[i] = mtlmaterials_.size() - 1;
737 material_map_.add_new(material, r_mtl_indices[i]);
738 }
739 }
740 return r_mtl_indices;
741}
742
743const char *MTLWriter::mtlmaterial_name(int index)
744{
745 if (index < 0 || index >= mtlmaterials_.size()) {
746 return nullptr;
747 }
748 return mtlmaterials_[index].name.c_str();
749}
750
751
752} // namespace blender::io::obj
const char * BKE_blender_version_string(void)
Definition blender.cc:139
#define BLI_STATIC_ASSERT(a, msg)
Definition BLI_assert.h:87
#define BLI_assert(a)
Definition BLI_assert.h:50
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
#define STRNCPY(dst, src)
Definition BLI_string.h:593
#define ARRAY_SIZE(arr)
@ CU_NURB_CYCLIC
@ CU_NURB_ENDPOINT
ePathReferenceMode
in reality light always falls off quadratically Particle Retrieve the data of the particle that spawned the object for example to give variation to multiple instances of an object Point Retrieve information about points in a point cloud Retrieve the edges of an object as it appears to Cycles topology will always appear triangulated Convert a blackbody temperature to an RGB value Normal Generate a perturbed normal from an RGB normal map image Typically used for faking highly detailed surfaces Generate an OSL shader from a file or text data block Image Sample an image file as a texture Gabor Generate Gabor noise Gradient Generate interpolated color and intensity values based on the input vector Magic Generate a psychedelic color texture Voronoi Generate Worley noise based on the distance to random points Typically used to generate textures such as or biological cells Brick Generate a procedural texture producing bricks Texture Retrieve multiple types of texture coordinates nTypically used as inputs for texture nodes Vector Convert a or normal between and object coordinate space Combine Create a color from its and value channels Color Retrieve a color attribute
SIMD_FORCE_INLINE btVector3 transform(const btVector3 &point) const
AttributeSet attributes
constexpr int64_t size() const
Definition BLI_span.hh:253
constexpr int64_t size() const
Definition BLI_span.hh:253
constexpr IndexRange index_range() const
Definition BLI_span.hh:402
constexpr bool is_empty() const
constexpr const char * data() 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
int total_spline_control_points(int spline_index) const
short get_nurbs_flagu(int spline_index) const
float3 vertex_coordinates(int spline_index, int vertex_index, float global_scale) const
int get_nurbs_degree(int spline_index) const
const char * get_curve_name() 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
const Span< float2 > get_uv_coords() const
Span< int > get_face_normal_indices(const int face_index) const
Span< int > calc_face_vert_indices(const int face_index) const
int ith_smooth_group(int face_index) const
const Mesh * get_mesh() 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
CCL_NAMESPACE_BEGIN struct Options options
draw_view push_constant(Type::INT, "radiance_src") .push_constant(Type capture_info_buf storage_buf(1, Qualifier::READ, "ObjectBounds", "bounds_buf[]") .push_constant(Type draw_view int
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
int count
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:12
void path_reference_copy(const Set< std::pair< std::string, std::string > > &copy_set)
Definition path_util.cc:61
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:95
MatBase< float, 4, 4 > float4x4
VecBase< int32_t, 2 > int2
ColorSceneLinear4f< eAlpha::Premultiplied > ColorGeometry4f
Definition BLI_color.hh:337
VecBase< float, 3 > float3
signed short int16_t
Definition stdint.h:76
__int64 int64_t
Definition stdint.h:89
char * active_color_attribute
blender::BitVector is_loose_bits
const MTLTexMap & tex_map_of_type(MTLTexMapType key) const
char * buffers[2]