Blender V4.5
obj_import_file_reader.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 "BKE_report.hh"
10
11#include "BLI_fileops.hh"
12#include "BLI_map.hh"
13#include "BLI_math_vector.h"
15#include "BLI_string.h"
16#include "BLI_string_ref.hh"
17#include "BLI_vector.hh"
18
19#include "IO_string_utils.hh"
20
21#include "obj_export_mtl.hh"
23
24#include <algorithm>
25#include <charconv>
26
27#include "CLG_log.h"
28static CLG_LogRef LOG = {"io.obj"};
29
30namespace blender::io::obj {
31
32using std::string;
33
38static Geometry *create_geometry(Geometry *const prev_geometry,
39 const eGeometryType new_type,
40 StringRef name,
41 Vector<std::unique_ptr<Geometry>> &r_all_geometries)
42{
43 auto new_geometry = [&]() {
44 r_all_geometries.append(std::make_unique<Geometry>());
45 Geometry *g = r_all_geometries.last().get();
46 g->geom_type_ = new_type;
47 g->geometry_name_ = name.is_empty() ? "New object" : name;
48 return g;
49 };
50
51 if (prev_geometry && prev_geometry->geom_type_ == GEOM_MESH) {
52 /* After the creation of a Geometry instance, at least one element has been found in the OBJ
53 * file that indicates that it is a mesh (faces or edges). */
54 if (!prev_geometry->face_elements_.is_empty() || !prev_geometry->edges_.is_empty()) {
55 return new_geometry();
56 }
57 if (new_type == GEOM_MESH) {
58 /* A Geometry created initially with a default name now found its name. */
59 prev_geometry->geometry_name_ = name;
60 return prev_geometry;
61 }
62 if (new_type == GEOM_CURVE) {
63 /* The object originally created is not a mesh now that curve data
64 * follows the vertex coordinates list. */
65 prev_geometry->geom_type_ = GEOM_CURVE;
66 return prev_geometry;
67 }
68 }
69
70 return new_geometry();
71}
72
73static void geom_add_vertex(const char *p, const char *end, GlobalVertices &r_global_vertices)
74{
75 r_global_vertices.flush_mrgb_block();
76 float3 vert;
77 p = parse_floats(p, end, 0.0f, vert, 3);
78 r_global_vertices.vertices.append(vert);
79 /* OBJ extension: `xyzrgb` vertex colors, when the vertex position
80 * is followed by 3 more RGB color components. See
81 * http://paulbourke.net/dataformats/obj/colour.html */
82 if (p < end) {
83 float3 srgb;
84 p = parse_floats(p, end, -1.0f, srgb, 3);
85 if (srgb.x >= 0 && srgb.y >= 0 && srgb.z >= 0) {
86 float3 linear;
87 srgb_to_linearrgb_v3_v3(linear, srgb);
88 r_global_vertices.set_vertex_color(r_global_vertices.vertices.size() - 1, linear);
89 }
90 else if (srgb.x > 0) {
91 /* Treats value in srgb.x as weight. */
92 r_global_vertices.set_vertex_weight(r_global_vertices.vertices.size() - 1, srgb.x);
93 }
94 }
95 UNUSED_VARS(p);
96}
97
98static void geom_add_mrgb_colors(const char *p, const char *end, GlobalVertices &r_global_vertices)
99{
100 /* MRGB color extension, in the form of
101 * "#MRGB MMRRGGBBMMRRGGBB ..."
102 * http://paulbourke.net/dataformats/obj/colour.html */
103 p = drop_whitespace(p, end);
104 const int mrgb_length = 8;
105 while (p + mrgb_length <= end) {
106 uint32_t value = 0;
107 std::from_chars_result res = std::from_chars(p, p + mrgb_length, value, 16);
108 if (ELEM(res.ec, std::errc::invalid_argument, std::errc::result_out_of_range)) {
109 return;
110 }
111 uchar srgb[4];
112 srgb[0] = (value >> 16) & 0xFF;
113 srgb[1] = (value >> 8) & 0xFF;
114 srgb[2] = value & 0xFF;
115 srgb[3] = 0xFF;
116 float linear[4];
117 srgb_to_linearrgb_uchar4(linear, srgb);
118
119 r_global_vertices.mrgb_block.append(float3(linear[0], linear[1], linear[2]));
120
121 p += mrgb_length;
122 }
123}
124
125static void geom_add_vertex_normal(const char *p,
126 const char *end,
127 GlobalVertices &r_global_vertices)
128{
129 float3 normal;
130 parse_floats(p, end, 0.0f, normal, 3);
131 /* Normals can be printed with only several digits in the file,
132 * making them ever-so-slightly non unit length. Make sure they are
133 * normalized. */
134 normalize_v3(normal);
135 r_global_vertices.vert_normals.append(normal);
136}
137
138static void geom_add_uv_vertex(const char *p, const char *end, GlobalVertices &r_global_vertices)
139{
140 float2 uv;
141 parse_floats(p, end, 0.0f, uv, 2);
142 r_global_vertices.uv_vertices.append(uv);
143}
144
152static const char *parse_vertex_index(const char *p, const char *end, size_t n_elems, int &r_index)
153{
154 p = parse_int(p, end, INT32_MAX, r_index, false);
155 if (r_index != INT32_MAX) {
156 r_index += r_index < 0 ? n_elems : -1;
157 if (r_index < 0 || r_index >= n_elems) {
158 CLOG_WARN(&LOG, "Invalid vertex index %i (valid range [0, %zu))", r_index, n_elems);
159 r_index = INT32_MAX;
160 }
161 }
162 return p;
163}
164
174static void geom_add_polyline(Geometry *geom,
175 const char *p,
176 const char *end,
177 GlobalVertices &r_global_vertices)
178{
179 int last_vertex_index;
180 p = drop_whitespace(p, end);
181 p = parse_vertex_index(p, end, r_global_vertices.vertices.size(), last_vertex_index);
182
183 if (last_vertex_index == INT32_MAX) {
184 CLOG_WARN(&LOG, "Skipping invalid OBJ polyline.");
185 return;
186 }
187 geom->track_vertex_index(last_vertex_index);
188
189 while (p < end) {
190 int vertex_index;
191
192 /* Lines can contain texture coordinate indices, just ignore them. */
193 p = drop_non_whitespace(p, end);
194 /* Skip whitespace to get to the next vertex. */
195 p = drop_whitespace(p, end);
196
197 p = parse_vertex_index(p, end, r_global_vertices.vertices.size(), vertex_index);
198 if (vertex_index == INT32_MAX) {
199 break;
200 }
201
202 geom->edges_.append({last_vertex_index, vertex_index});
203 geom->track_vertex_index(vertex_index);
204 last_vertex_index = vertex_index;
205 }
206}
207
208static void geom_add_polygon(Geometry *geom,
209 const char *p,
210 const char *end,
211 const GlobalVertices &global_vertices,
212 const int material_index,
213 const int group_index,
214 const bool shaded_smooth)
215{
216 FaceElem curr_face;
217 curr_face.shaded_smooth = shaded_smooth;
218 curr_face.material_index = material_index;
219 if (group_index >= 0) {
220 curr_face.vertex_group_index = group_index;
221 geom->has_vertex_groups_ = true;
222 }
223
224 const int orig_corners_size = geom->face_corners_.size();
225 curr_face.start_index_ = orig_corners_size;
226
227 bool face_valid = true;
228 p = drop_whitespace(p, end);
229 while (p < end && face_valid) {
230 FaceCorner corner;
231 bool got_uv = false, got_normal = false;
232 /* Parse vertex index. */
233 p = parse_int(p, end, INT32_MAX, corner.vert_index, false);
234
235 /* Skip parsing when we reach start of the comment. */
236 if (*p == '#') {
237 break;
238 }
239
240 face_valid &= corner.vert_index != INT32_MAX;
241 if (p < end && *p == '/') {
242 /* Parse UV index. */
243 ++p;
244 if (p < end && *p != '/') {
245 p = parse_int(p, end, INT32_MAX, corner.uv_vert_index, false);
246 got_uv = corner.uv_vert_index != INT32_MAX;
247 }
248 /* Parse normal index. */
249 if (p < end && *p == '/') {
250 ++p;
251 p = parse_int(p, end, INT32_MAX, corner.vertex_normal_index, false);
252 got_normal = corner.vertex_normal_index != INT32_MAX;
253 }
254 }
255 /* Always keep stored indices non-negative and zero-based. */
256 corner.vert_index += corner.vert_index < 0 ? global_vertices.vertices.size() : -1;
257 if (corner.vert_index < 0 || corner.vert_index >= global_vertices.vertices.size()) {
258 CLOG_WARN(&LOG,
259 "Invalid vertex index %i (valid range [0, %zu)), ignoring face",
260 corner.vert_index,
261 size_t(global_vertices.vertices.size()));
262 face_valid = false;
263 }
264 else {
265 geom->track_vertex_index(corner.vert_index);
266 }
267 /* Ignore UV index, if the geometry does not have any UVs (#103212). */
268 if (got_uv && !global_vertices.uv_vertices.is_empty()) {
269 corner.uv_vert_index += corner.uv_vert_index < 0 ? global_vertices.uv_vertices.size() : -1;
270 if (corner.uv_vert_index < 0 || corner.uv_vert_index >= global_vertices.uv_vertices.size()) {
271 CLOG_WARN(&LOG,
272 "Invalid UV index %i (valid range [0, %zu)), ignoring face",
273 corner.uv_vert_index,
274 size_t(global_vertices.uv_vertices.size()));
275 face_valid = false;
276 }
277 }
278 /* Ignore corner normal index, if the geometry does not have any normals.
279 * Some obj files out there do have face definitions that refer to normal indices,
280 * without any normals being present (#98782). */
281 if (got_normal && !global_vertices.vert_normals.is_empty()) {
282 corner.vertex_normal_index += corner.vertex_normal_index < 0 ?
283 global_vertices.vert_normals.size() :
284 -1;
285 if (corner.vertex_normal_index < 0 ||
286 corner.vertex_normal_index >= global_vertices.vert_normals.size())
287 {
288 CLOG_WARN(&LOG,
289 "Invalid normal index %i (valid range [0, %zu)), ignoring face",
290 corner.vertex_normal_index,
291 size_t(global_vertices.vert_normals.size()));
292 face_valid = false;
293 }
294 }
295 geom->face_corners_.append(corner);
296 curr_face.corner_count_++;
297
298 /* Some files contain extra stuff per face (e.g. 4 indices); skip any remainder (#103441). */
299 p = drop_non_whitespace(p, end);
300 /* Skip whitespace to get to the next face corner. */
301 p = drop_whitespace(p, end);
302 }
303
304 if (face_valid) {
305 geom->face_elements_.append(curr_face);
306 geom->total_corner_ += curr_face.corner_count_;
307 }
308 else {
309 /* Remove just-added corners for the invalid face. */
310 geom->face_corners_.resize(orig_corners_size);
311 geom->has_invalid_faces_ = true;
312 }
313}
314
316 const char *p,
317 const char *end,
318 const StringRef group_name,
319 Vector<std::unique_ptr<Geometry>> &r_all_geometries)
320{
321 p = drop_whitespace(p, end);
322 if (!StringRef(p, end).startswith("bspline") && !StringRef(p, end).startswith("rat bspline")) {
323 CLOG_WARN(&LOG, "Curve type not supported: '%s'", std::string(p, end).c_str());
324 return geom;
325 }
326 geom = create_geometry(geom, GEOM_CURVE, group_name, r_all_geometries);
327 geom->nurbs_element_.group_ = group_name;
328 return geom;
329}
330
331static void geom_set_curve_degree(Geometry *geom, const char *p, const char *end)
332{
333 parse_int(p, end, 3, geom->nurbs_element_.degree);
334}
335
337 const char *p,
338 const char *end,
339 const GlobalVertices &global_vertices)
340{
341 /* Parse curve parameter range. */
342 p = parse_floats(p, end, 0, geom->nurbs_element_.range, 2);
343 /* Parse indices. */
344 while (p < end) {
345 int index;
346 p = parse_int(p, end, INT32_MAX, index);
347 if (index == INT32_MAX) {
348 return;
349 }
350 /* Always keep stored indices non-negative and zero-based. */
351 index += index < 0 ? global_vertices.vertices.size() : -1;
353 }
354}
355
356static void geom_add_curve_parameters(Geometry *geom, const char *p, const char *end)
357{
358 p = drop_whitespace(p, end);
359 if (p == end) {
360 CLOG_ERROR(&LOG, "Invalid OBJ curve parm line");
361 return;
362 }
363 if (*p != 'u') {
364 CLOG_WARN(&LOG, "OBJ curve surfaces are not supported, found '%c'", *p);
365 return;
366 }
367 ++p;
368
369 while (p < end) {
370 float val;
371 p = parse_float(p, end, FLT_MAX, val);
372 if (val != FLT_MAX) {
373 geom->nurbs_element_.parm.append(val);
374 }
375 else {
376 CLOG_ERROR(&LOG, "OBJ curve parm line has invalid number");
377 return;
378 }
379 }
380}
381
382static void geom_update_group(const StringRef rest_line, std::string &r_group_name)
383{
384 if (rest_line.find("off") != string::npos || rest_line.find("null") != string::npos ||
385 rest_line.find("default") != string::npos)
386 {
387 /* Set group for future elements like faces or curves to empty. */
388 r_group_name = "";
389 return;
390 }
391 r_group_name = rest_line;
392}
393
394static void geom_update_smooth_group(const char *p, const char *end, bool &r_state_shaded_smooth)
395{
396 p = drop_whitespace(p, end);
397 /* Some implementations use "0" and "null" too, in addition to "off". */
398 const StringRef line = StringRef(p, end);
399 if (line == "0" || line.startswith("off") || line.startswith("null")) {
400 r_state_shaded_smooth = false;
401 return;
402 }
403
404 int smooth = 0;
405 parse_int(p, end, 0, smooth);
406 r_state_shaded_smooth = smooth != 0;
407}
408
409static void geom_new_object(const char *p,
410 const char *end,
411 bool &r_state_shaded_smooth,
412 std::string &r_state_group_name,
413 int &r_state_material_index,
414 Geometry *&r_curr_geom,
415 Vector<std::unique_ptr<Geometry>> &r_all_geometries)
416{
417 r_state_shaded_smooth = false;
418 r_state_group_name = "";
419 /* Reset object-local material index that's used in face information.
420 * NOTE: do not reset the material name; that has to carry over
421 * into the next object if needed. */
422 r_state_material_index = -1;
423 r_curr_geom = create_geometry(
424 r_curr_geom, GEOM_MESH, StringRef(p, end).trim(), r_all_geometries);
425}
426
427OBJParser::OBJParser(const OBJImportParams &import_params, size_t read_buffer_size)
428 : import_params_(import_params), read_buffer_size_(read_buffer_size)
429{
430 obj_file_ = BLI_fopen(import_params_.filepath, "rb");
431 if (!obj_file_) {
432 CLOG_ERROR(&LOG, "Cannot read from OBJ file:'%s'.", import_params_.filepath);
433 BKE_reportf(import_params_.reports,
434 RPT_ERROR,
435 "OBJ Import: Cannot open file '%s'",
436 import_params_.filepath);
437 return;
438 }
439}
440
442{
443 if (obj_file_) {
444 fclose(obj_file_);
445 }
446}
447
448/* If line starts with keyword followed by whitespace, returns true and drops it from the line. */
449static bool parse_keyword(const char *&p, const char *end, StringRef keyword)
450{
451 const size_t keyword_len = keyword.size();
452 if (end - p < keyword_len + 1) {
453 return false;
454 }
455 if (memcmp(p, keyword.data(), keyword_len) != 0) {
456 return false;
457 }
458 /* Treat any ASCII control character as white-space;
459 * don't use `isspace()` for performance reasons. */
460 if (p[keyword_len] > ' ') {
461 return false;
462 }
463 p += keyword_len + 1;
464 return true;
465}
466
467/* Special case: if there were no faces/edges in any geometries,
468 * treat all the vertices as a point cloud. */
470 const Span<std::unique_ptr<Geometry>> all_geometries,
471 const GlobalVertices &global_vertices)
472{
473 if (!global_vertices.vertices.is_empty() && geom && geom->geom_type_ == GEOM_MESH) {
474 if (std::all_of(all_geometries.begin(),
475 all_geometries.end(),
476 [](const std::unique_ptr<Geometry> &g) { return g->get_vertex_count() == 0; }))
477 {
478 geom->track_all_vertices(global_vertices.vertices.size());
479 }
480 }
481}
482
483void OBJParser::parse(Vector<std::unique_ptr<Geometry>> &r_all_geometries,
484 GlobalVertices &r_global_vertices)
485{
486 if (!obj_file_) {
487 return;
488 }
489
490 /* Use the filename as the default name given to the initial object. */
491 char ob_name[FILE_MAXFILE];
492 STRNCPY(ob_name, BLI_path_basename(import_params_.filepath));
494
495 Geometry *curr_geom = create_geometry(nullptr, GEOM_MESH, ob_name, r_all_geometries);
496
497 /* State variables: once set, they remain the same for the remaining
498 * elements in the object. */
499 bool state_shaded_smooth = false;
500 string state_group_name;
501 int state_group_index = -1;
502 string state_material_name;
503 int state_material_index = -1;
504
505 /* Read the input file in chunks. We need up to twice the possible chunk size,
506 * to possibly store remainder of the previous input line that got broken mid-chunk. */
507 Array<char> buffer(read_buffer_size_ * 2);
508
509 size_t buffer_offset = 0;
510 size_t line_number = 0;
511 while (true) {
512 /* Read a chunk of input from the file. */
513 size_t bytes_read = fread(buffer.data() + buffer_offset, 1, read_buffer_size_, obj_file_);
514 if (bytes_read == 0 && buffer_offset == 0) {
515 break; /* No more data to read. */
516 }
517
518 /* Take care of line continuations now (turn them into spaces);
519 * the rest of the parsing code does not need to worry about them anymore. */
520 fixup_line_continuations(buffer.data() + buffer_offset,
521 buffer.data() + buffer_offset + bytes_read);
522
523 /* Ensure buffer ends in a newline. */
524 if (bytes_read < read_buffer_size_) {
525 if (bytes_read == 0 || buffer[buffer_offset + bytes_read - 1] != '\n') {
526 buffer[buffer_offset + bytes_read] = '\n';
527 bytes_read++;
528 }
529 }
530
531 size_t buffer_end = buffer_offset + bytes_read;
532 if (buffer_end == 0) {
533 break;
534 }
535
536 /* Find last newline. */
537 size_t last_nl = buffer_end;
538 while (last_nl > 0) {
539 --last_nl;
540 if (buffer[last_nl] == '\n') {
541 break;
542 }
543 }
544 if (buffer[last_nl] != '\n') {
545 /* Whole line did not fit into our read buffer. Warn and exit. */
547 "OBJ file contains a line #%zu that is too long (max. length %zu)",
548 line_number,
549 read_buffer_size_);
550 break;
551 }
552 ++last_nl;
553
554 /* Parse the buffer (until last newline) that we have so far,
555 * line by line. */
556 StringRef buffer_str{buffer.data(), int64_t(last_nl)};
557 while (!buffer_str.is_empty()) {
558 StringRef line = read_next_line(buffer_str);
559 const char *p = line.begin(), *end = line.end();
560 p = drop_whitespace(p, end);
561 ++line_number;
562 if (p == end) {
563 continue;
564 }
565 /* Most common things that start with 'v': vertices, normals, UVs. */
566 if (*p == 'v') {
567 if (parse_keyword(p, end, "v")) {
568 geom_add_vertex(p, end, r_global_vertices);
569 }
570 else if (parse_keyword(p, end, "vn")) {
571 geom_add_vertex_normal(p, end, r_global_vertices);
572 }
573 else if (parse_keyword(p, end, "vt")) {
574 geom_add_uv_vertex(p, end, r_global_vertices);
575 }
576 }
577 /* Faces. */
578 else if (parse_keyword(p, end, "f")) {
579 /* If we don't have a material index assigned yet, get one.
580 * It means "usemtl" state came from the previous object. */
581 if (state_material_index == -1 && !state_material_name.empty() &&
582 curr_geom->material_indices_.is_empty())
583 {
584 curr_geom->material_indices_.add_new(state_material_name, 0);
585 curr_geom->material_order_.append(state_material_name);
586 state_material_index = 0;
587 }
588
589 geom_add_polygon(curr_geom,
590 p,
591 end,
592 r_global_vertices,
593 state_material_index,
594 state_group_index,
595 state_shaded_smooth);
596 }
597 /* Faces. */
598 else if (parse_keyword(p, end, "l")) {
599 geom_add_polyline(curr_geom, p, end, r_global_vertices);
600 }
601 /* Objects. */
602 else if (parse_keyword(p, end, "o")) {
603 if (import_params_.use_split_objects) {
605 end,
606 state_shaded_smooth,
607 state_group_name,
608 state_material_index,
609 curr_geom,
610 r_all_geometries);
611 }
612 }
613 /* Groups. */
614 else if (parse_keyword(p, end, "g")) {
615 if (import_params_.use_split_groups) {
617 end,
618 state_shaded_smooth,
619 state_group_name,
620 state_material_index,
621 curr_geom,
622 r_all_geometries);
623 }
624 else {
625 geom_update_group(StringRef(p, end).trim(), state_group_name);
626 int new_index = curr_geom->group_indices_.size();
627 state_group_index = curr_geom->group_indices_.lookup_or_add(state_group_name, new_index);
628 if (new_index == state_group_index) {
629 curr_geom->group_order_.append(state_group_name);
630 }
631 }
632 }
633 /* Smoothing groups. */
634 else if (parse_keyword(p, end, "s")) {
635 geom_update_smooth_group(p, end, state_shaded_smooth);
636 }
637 /* Materials and their libraries. */
638 else if (parse_keyword(p, end, "usemtl")) {
639 state_material_name = StringRef(p, end).trim();
640 int new_mat_index = curr_geom->material_indices_.size();
641 state_material_index = curr_geom->material_indices_.lookup_or_add(state_material_name,
642 new_mat_index);
643 if (new_mat_index == state_material_index) {
644 curr_geom->material_order_.append(state_material_name);
645 }
646 }
647 else if (parse_keyword(p, end, "mtllib")) {
648 add_mtl_library(StringRef(p, end).trim());
649 }
650 else if (parse_keyword(p, end, "#MRGB")) {
651 geom_add_mrgb_colors(p, end, r_global_vertices);
652 }
653 /* Comments. */
654 else if (*p == '#') {
655 /* Nothing to do. */
656 }
657 /* Curve related things. */
658 else if (parse_keyword(p, end, "cstype")) {
659 curr_geom = geom_set_curve_type(curr_geom, p, end, state_group_name, r_all_geometries);
660 }
661 else if (parse_keyword(p, end, "deg")) {
662 geom_set_curve_degree(curr_geom, p, end);
663 }
664 else if (parse_keyword(p, end, "curv")) {
665 geom_add_curve_vertex_indices(curr_geom, p, end, r_global_vertices);
666 }
667 else if (parse_keyword(p, end, "parm")) {
668 geom_add_curve_parameters(curr_geom, p, end);
669 }
670 else if (StringRef(p, end).startswith("end")) {
671 /* End of curve definition, nothing else to do. */
672 }
673 else {
674 CLOG_WARN(&LOG, "OBJ element not recognized: '%s'", std::string(p, end).c_str());
675 }
676 }
677
678 /* We might have a line that was cut in the middle by the previous buffer;
679 * copy it over for next chunk reading. */
680 size_t left_size = buffer_end - last_nl;
681 memmove(buffer.data(), buffer.data() + last_nl, left_size);
682 buffer_offset = left_size;
683 }
684
685 r_global_vertices.flush_mrgb_block();
686 use_all_vertices_if_no_faces(curr_geom, r_all_geometries, r_global_vertices);
687 add_default_mtl_library();
688}
689
690static MTLTexMapType mtl_line_start_to_texture_type(const char *&p, const char *end)
691{
692 if (parse_keyword(p, end, "map_Kd")) {
694 }
695 if (parse_keyword(p, end, "map_Ks")) {
697 }
698 if (parse_keyword(p, end, "map_Ns")) {
700 }
701 if (parse_keyword(p, end, "map_d")) {
703 }
704 if (parse_keyword(p, end, "refl") || parse_keyword(p, end, "map_refl")) {
706 }
707 if (parse_keyword(p, end, "map_Ke")) {
709 }
710 if (parse_keyword(p, end, "bump") || parse_keyword(p, end, "map_Bump") ||
711 parse_keyword(p, end, "map_bump"))
712 {
714 }
715 if (parse_keyword(p, end, "map_Pr")) {
717 }
718 if (parse_keyword(p, end, "map_Pm")) {
720 }
721 if (parse_keyword(p, end, "map_Ps")) {
723 }
725}
726
727static const std::pair<StringRef, int> unsupported_texture_options[] = {
728 {"-blendu", 1},
729 {"-blendv", 1},
730 {"-boost", 1},
731 {"-cc", 1},
732 {"-clamp", 1},
733 {"-imfchan", 1},
734 {"-mm", 2},
735 {"-t", 3},
736 {"-texres", 1},
737};
738
739static bool parse_texture_option(const char *&p,
740 const char *end,
741 MTLMaterial *material,
742 MTLTexMap &tex_map)
743{
744 p = drop_whitespace(p, end);
745 if (parse_keyword(p, end, "-o")) {
746 p = parse_floats(p, end, 0.0f, tex_map.translation, 3, true);
747 return true;
748 }
749 if (parse_keyword(p, end, "-s")) {
750 p = parse_floats(p, end, 1.0f, tex_map.scale, 3, true);
751 return true;
752 }
753 if (parse_keyword(p, end, "-bm")) {
754 p = parse_float(p, end, 1.0f, material->normal_strength, true, true);
755 return true;
756 }
757 if (parse_keyword(p, end, "-type")) {
758 p = drop_whitespace(p, end);
759 /* Only sphere is supported. */
761 const StringRef line = StringRef(p, end);
762 if (!line.startswith("sphere")) {
763 CLOG_WARN(&LOG,
764 "Only the 'sphere' MTL projection type is supported, found: '%s'",
765 std::string(line).c_str());
766 }
767 p = drop_non_whitespace(p, end);
768 return true;
769 }
770 /* Check for unsupported options and skip them. */
771 for (const auto &opt : unsupported_texture_options) {
772 if (parse_keyword(p, end, opt.first)) {
773 /* Drop the arguments. */
774 for (int i = 0; i < opt.second; ++i) {
775 p = drop_whitespace(p, end);
776 p = drop_non_whitespace(p, end);
777 }
778 return true;
779 }
780 }
781
782 return false;
783}
784
785static void parse_texture_map(const char *p,
786 const char *end,
787 MTLMaterial *material,
788 const char *mtl_dir_path)
789{
790 const StringRef line = StringRef(p, end);
791 bool is_map = line.startswith("map_");
792 bool is_refl = line.startswith("refl");
793 bool is_bump = line.startswith("bump");
794 if (!is_map && !is_refl && !is_bump) {
795 return;
796 }
798 if (key == MTLTexMapType::Count) {
799 /* No supported texture map found. */
800 CLOG_WARN(&LOG, "MTL texture map type not supported: '%s'", std::string(line).c_str());
801 return;
802 }
803 MTLTexMap &tex_map = material->tex_map_of_type(key);
804 tex_map.mtl_dir_path = mtl_dir_path;
805
806 /* Parse texture map options. */
807 while (parse_texture_option(p, end, material, tex_map)) {
808 }
809
810 /* What remains is the image path. */
811 tex_map.image_path = StringRef(p, end).trim();
812}
813
815{
816 return mtl_libraries_;
817}
818
819void OBJParser::add_mtl_library(StringRef path)
820{
821 /* Remove any quotes from start and end (#67266, #97794). */
822 if (path.size() > 2 && path.startswith("\"") && path.endswith("\"")) {
823 path = path.drop_prefix(1).drop_suffix(1);
824 }
825
826 if (!mtl_libraries_.contains(path)) {
827 mtl_libraries_.append(path);
828 }
829}
830
831void OBJParser::add_default_mtl_library()
832{
833 /* Add any existing `.mtl` file that's with the same base name as the `.obj` file
834 * into candidate `.mtl` files to search through. This is not technically following the
835 * spec, but the old python importer was doing it, and there are user files out there
836 * that contain "mtllib bar.mtl" for a foo.obj, and depend on finding materials
837 * from foo.mtl (see #97757). */
838 char mtl_file_path[FILE_MAX];
839 STRNCPY(mtl_file_path, import_params_.filepath);
840 BLI_path_extension_replace(mtl_file_path, sizeof(mtl_file_path), ".mtl");
841 if (BLI_exists(mtl_file_path)) {
842 char mtl_file_base[FILE_MAX];
843 BLI_path_split_file_part(mtl_file_path, mtl_file_base, sizeof(mtl_file_base));
844 add_mtl_library(mtl_file_base);
845 }
846}
847
849{
850 char obj_file_dir[FILE_MAXDIR];
851 BLI_path_split_dir_part(obj_filepath.data(), obj_file_dir, FILE_MAXDIR);
852 BLI_path_join(mtl_file_path_, FILE_MAX, obj_file_dir, mtl_library.data());
853 BLI_path_split_dir_part(mtl_file_path_, mtl_dir_path_, FILE_MAXDIR);
854}
855
856void MTLParser::parse_and_store(Map<string, std::unique_ptr<MTLMaterial>> &r_materials)
857{
858 size_t buffer_len;
859 void *buffer = BLI_file_read_text_as_mem(mtl_file_path_, 0, &buffer_len);
860 if (buffer == nullptr) {
861 CLOG_ERROR(&LOG, "OBJ import: cannot read from MTL file: '%s'", mtl_file_path_);
862 return;
863 }
864
865 MTLMaterial *material = nullptr;
866
867 StringRef buffer_str{(const char *)buffer, int64_t(buffer_len)};
868 while (!buffer_str.is_empty()) {
869 const StringRef line = read_next_line(buffer_str);
870 const char *p = line.begin(), *end = line.end();
871 p = drop_whitespace(p, end);
872 if (p == end) {
873 continue;
874 }
875
876 if (parse_keyword(p, end, "newmtl")) {
877 StringRef mat_name = StringRef(p, end).trim();
878 if (r_materials.contains(mat_name)) {
879 material = nullptr;
880 }
881 else {
882 material =
883 r_materials.lookup_or_add(string(mat_name), std::make_unique<MTLMaterial>()).get();
884 }
885 }
886 else if (material != nullptr) {
887 if (parse_keyword(p, end, "Ns")) {
888 parse_float(p, end, 324.0f, material->spec_exponent);
889 }
890 else if (parse_keyword(p, end, "Ka")) {
891 parse_floats(p, end, 0.0f, material->ambient_color, 3);
892 }
893 else if (parse_keyword(p, end, "Kd")) {
894 parse_floats(p, end, 0.8f, material->color, 3);
895 }
896 else if (parse_keyword(p, end, "Ks")) {
897 parse_floats(p, end, 0.5f, material->spec_color, 3);
898 }
899 else if (parse_keyword(p, end, "Ke")) {
900 parse_floats(p, end, 0.0f, material->emission_color, 3);
901 }
902 else if (parse_keyword(p, end, "Ni")) {
903 parse_float(p, end, 1.45f, material->ior);
904 }
905 else if (parse_keyword(p, end, "d")) {
906 parse_float(p, end, 1.0f, material->alpha);
907 }
908 else if (parse_keyword(p, end, "illum")) {
909 /* Some files incorrectly use a float (#60135). */
910 float val;
911 parse_float(p, end, 1.0f, val);
912 material->illum_mode = val;
913 }
914 else if (parse_keyword(p, end, "Pr")) {
915 parse_float(p, end, 0.5f, material->roughness);
916 }
917 else if (parse_keyword(p, end, "Pm")) {
918 parse_float(p, end, 0.0f, material->metallic);
919 }
920 else if (parse_keyword(p, end, "Ps")) {
921 parse_float(p, end, 0.0f, material->sheen);
922 }
923 else if (parse_keyword(p, end, "Pc")) {
924 parse_float(p, end, 0.0f, material->cc_thickness);
925 }
926 else if (parse_keyword(p, end, "Pcr")) {
927 parse_float(p, end, 0.0f, material->cc_roughness);
928 }
929 else if (parse_keyword(p, end, "aniso")) {
930 parse_float(p, end, 0.0f, material->aniso);
931 }
932 else if (parse_keyword(p, end, "anisor")) {
933 parse_float(p, end, 0.0f, material->aniso_rot);
934 }
935 else if (parse_keyword(p, end, "Kt") || parse_keyword(p, end, "Tf")) {
936 parse_floats(p, end, 0.0f, material->transmit_color, 3);
937 }
938 else {
939 parse_texture_map(p, end, material, mtl_dir_path_);
940 }
941 }
942 }
943
944 MEM_freeN(buffer);
945}
946} // namespace blender::io::obj
void BKE_reportf(ReportList *reports, eReportType type, const char *format,...) ATTR_PRINTF_FORMAT(3
int BLI_exists(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition storage.cc:373
FILE * BLI_fopen(const char *filepath, const char *mode) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
void * BLI_file_read_text_as_mem(const char *filepath, size_t pad_bytes, size_t *r_size)
Definition storage.cc:524
File and directory operations.
MINLINE void srgb_to_linearrgb_uchar4(float linear[4], const unsigned char srgb[4])
void srgb_to_linearrgb_v3_v3(float linear[3], const float srgb[3])
MINLINE float normalize_v3(float n[3])
bool bool BLI_path_extension_strip(char *path) ATTR_NONNULL(1)
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
#define BLI_path_join(...)
void void void BLI_path_split_file_part(const char *filepath, 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
unsigned char uchar
#define UNUSED_VARS(...)
#define ELEM(...)
#define CLOG_ERROR(clg_ref,...)
Definition CLG_log.h:182
#define CLOG_WARN(clg_ref,...)
Definition CLG_log.h:181
@ SHD_PROJ_SPHERE
long long int int64_t
void add_new(const Key &key, const Value &value)
Definition BLI_map.hh:265
int64_t size() const
Definition BLI_map.hh:976
bool is_empty() const
Definition BLI_map.hh:986
Value & lookup_or_add(const Key &key, const Value &value)
Definition BLI_map.hh:588
bool contains(const T &value) const
void append(const T &value)
const T * data() const
Definition BLI_array.hh:301
constexpr int64_t find(char c, int64_t pos=0) const
constexpr const char * begin() const
constexpr const char * end() const
constexpr bool is_empty() const
constexpr bool startswith(StringRef prefix) const
constexpr bool endswith(StringRef suffix) const
constexpr int64_t size() const
constexpr StringRef trim() const
constexpr const char * data() const
constexpr StringRef drop_prefix(int64_t n) const
constexpr StringRef drop_suffix(int64_t n) const
void append(const T &value)
void parse_and_store(Map< std::string, std::unique_ptr< MTLMaterial > > &r_materials)
MTLParser(StringRefNull mtl_library_, StringRefNull obj_filepath)
Span< std::string > mtl_libraries() const
OBJParser(const OBJImportParams &import_params, size_t read_buffer_size)
void parse(Vector< std::unique_ptr< Geometry > > &r_all_geometries, GlobalVertices &r_global_vertices)
#define INT32_MAX
#define LOG(severity)
Definition log.h:32
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
static void use_all_vertices_if_no_faces(Geometry *geom, const Span< std::unique_ptr< Geometry > > all_geometries, const GlobalVertices &global_vertices)
static void geom_new_object(const char *p, const char *end, bool &r_state_shaded_smooth, std::string &r_state_group_name, int &r_state_material_index, Geometry *&r_curr_geom, Vector< std::unique_ptr< Geometry > > &r_all_geometries)
static void geom_add_curve_vertex_indices(Geometry *geom, const char *p, const char *end, const GlobalVertices &global_vertices)
static void geom_add_vertex(const char *p, const char *end, GlobalVertices &r_global_vertices)
static void geom_add_mrgb_colors(const char *p, const char *end, GlobalVertices &r_global_vertices)
static void parse_texture_map(const char *p, const char *end, MTLMaterial *material, const char *mtl_dir_path)
static void geom_add_uv_vertex(const char *p, const char *end, GlobalVertices &r_global_vertices)
static const char * parse_vertex_index(const char *p, const char *end, size_t n_elems, int &r_index)
static MTLTexMapType mtl_line_start_to_texture_type(const char *&p, const char *end)
static void geom_add_polyline(Geometry *geom, const char *p, const char *end, GlobalVertices &r_global_vertices)
static bool parse_keyword(const char *&p, const char *end, StringRef keyword)
static bool parse_texture_option(const char *&p, const char *end, MTLMaterial *material, MTLTexMap &tex_map)
static void geom_add_vertex_normal(const char *p, const char *end, GlobalVertices &r_global_vertices)
static void geom_add_curve_parameters(Geometry *geom, const char *p, const char *end)
static void geom_set_curve_degree(Geometry *geom, const char *p, const char *end)
static void geom_update_smooth_group(const char *p, const char *end, bool &r_state_shaded_smooth)
static void geom_update_group(const StringRef rest_line, std::string &r_group_name)
static Geometry * geom_set_curve_type(Geometry *geom, const char *p, const char *end, const StringRef group_name, Vector< std::unique_ptr< Geometry > > &r_all_geometries)
static const std::pair< StringRef, int > unsupported_texture_options[]
static void geom_add_polygon(Geometry *geom, const char *p, const char *end, const GlobalVertices &global_vertices, const int material_index, const int group_index, const bool shaded_smooth)
static Geometry * create_geometry(Geometry *const prev_geometry, const eGeometryType new_type, StringRef name, Vector< std::unique_ptr< Geometry > > &r_all_geometries)
const char * parse_float(const char *p, const char *end, float fallback, float &dst, bool skip_space, bool require_trailing_space)
const char * parse_floats(const char *p, const char *end, float fallback, float *dst, int count, bool require_trailing_space)
const char * drop_non_whitespace(const char *p, const char *end)
StringRef read_next_line(StringRef &buffer)
const char * parse_int(const char *p, const char *end, int fallback, int &dst, bool skip_space)
void fixup_line_continuations(char *p, char *end)
const char * drop_whitespace(const char *p, const char *end)
VecBase< float, 2 > float2
VecBase< float, 3 > float3
#define FLT_MAX
Definition stdcycles.h:14
char filepath[FILE_MAX]
Vector< std::string > material_order_
Vector< FaceCorner > face_corners_
Map< std::string, int > group_indices_
Vector< std::string > group_order_
Map< std::string, int > material_indices_
void set_vertex_weight(size_t index, float weight)
void set_vertex_color(size_t index, float3 color)
const MTLTexMap & tex_map_of_type(MTLTexMapType key) const
i
Definition text_draw.cc:230