1
1

Compare commits

...

455 Commits

Author SHA1 Message Date
e9bbfd0c8c Merge branch 'master' into soc-2020-io-performance 2021-10-30 15:37:05 -04:00
1aa953bd19 Merge branch 'master' into soc-2020-io-performance 2021-10-24 08:31:22 -04:00
fc171c1be9 Add more regression tests for new obj exporter.
This adds regression tests for exporting most of the blend files
in the io_tests/blend_geometry and io_tests/blend_scene tests.
A fix was necessary in the BlendfileLoadingBaseTest class to prevent
leakage of some global data generated when a loaded file has metaballs.
Also, had to fix the exporter to deal with empty curves.
2021-10-17 16:17:45 -04:00
96c80950a1 Merge branch 'master' into soc-2020-io-performance 2021-10-14 13:25:59 -04:00
6e9eebe286 Merge branch 'master' into soc-2020-io-performance 2021-09-19 16:49:59 -04:00
53e7a2cb07 Merge branch 'master' into soc-2020-io-performance 2021-09-18 10:03:45 -04:00
e3edf862a9 Fix mistake with last commit - it didn't compile. 2021-09-18 10:03:16 -04:00
5f9eea200d Change where new obj exporter writes temp files.
The code was writing relative to the test_release_dir flag,
but this didn't make a lot of sense.
Better to use Blender's tempdir mechanism, though since Blender
isn't fully initialized when using it for gtests, we have
to call the function to initialize the tempdir.
2021-08-29 13:54:39 -04:00
4a7eca10ab Merge branch 'master' into soc-2020-io-performance 2021-08-28 07:25:52 -04:00
d619c62157 Merge branch 'master' into soc-2020-io-performance 2021-08-22 13:22:18 -04:00
1eed0031ec Add framework to fast obj i/o to regression test whole files.
One tricky aspect is that we want to allow for the version string
part of the header to mismatch, so only check for file equality
after the first lines.
2021-08-15 16:23:28 -04:00
fef2b9e2eb Fix new obj exporter curve test.
Due to a previous fix I made to the transform matrix code,
the golden data for the curve coordinates were incorrect.
2021-07-31 20:57:42 -04:00
db254aa981 Merge branch 'master' into soc-2020-io-performance 2021-07-31 15:40:11 -04:00
40027f31b3 Consistent var naming, function usage. 2021-07-10 19:08:45 +05:30
2cb056af9a Merge branch 'master' into soc-2020-io-performance 2021-07-10 19:04:19 +05:30
22686b4ccf Export transform was wrongly calculated.
Several mistakes fixed: Blender's axes convention was wrongly stated;
the mat3_from_axis_conversion in C returns a transposed matrix,
probably because it was copied from Python; the axis transformation
wasn't properly applied to the location part of an object transform.
2021-06-06 20:16:28 -07:00
984ab4719d Fix crash when UVs are not asked to be exported. 2021-06-05 20:56:20 -07:00
51ea8dc16d Fix (added argument) after master merge. 2021-06-05 12:25:00 -07:00
7a60565e54 Merge branch 'master' into soc-2020-io-performance 2021-06-05 11:33:11 -07:00
30ee3a1d5d Merge branch 'master' into soc-2020-io-performance 2021-05-09 09:25:36 -04:00
13be5a0e9b Merge branch 'master' into soc-2020-io-performance 2021-04-25 13:14:24 -04:00
1100dd8ad1 Merge branch 'master' into soc-2020-io-performance 2021-04-18 16:13:57 -04:00
ab97b22a1a Create test OBJ files in temporary place. 2021-04-18 15:59:37 -04:00
b3adbd8ed8 Fix test to not use Ankit's directory name. 2021-04-17 08:02:44 -04:00
b985418557 Merge branch 'master' into soc-2020-io-performance 2021-04-11 11:32:07 -04:00
fec16d4a8b Progress towards making material output the same as the python exporter.
The python exporter was stricter about what Principled BSDF node it
would use -- it only wanted one directly attached to the Material Output.
Also, the Python exporter sets the ambient to (1,1,1) if no reflections.
And there was a mistake in how the C++ exporter set the illumination model.

Also printed out the components in the same order as the python one,
for easier comparison.
2021-03-28 18:15:04 -04:00
ccc97b664c Fixed problem of repeated same materials output in MTL file.
The code was writing out all of the materials for each object.
Refactored so that the materials for all objects are gathered
together, deduping, and writing out at the end of a frame.
2021-03-28 08:03:17 -04:00
bfea06a639 Fix a bad default assignment and an unused parameter. 2021-03-27 08:52:57 -04:00
6af509f5a5 Merge branch 'master' into soc-2020-io-performance 2021-03-26 08:56:24 -04:00
85ab8eaa88 Put the blender file basename in comment in the header of the .mtl file. 2021-03-20 15:57:13 -04:00
dc68998aad Merge branch 'master' into soc-2020-io-performance 2021-03-20 13:30:59 -04:00
8268e687b0 Fixed up the TODOs re materials left when making it compile again.
Also, fixed a place where a newline belonged in material output.
2021-03-14 16:05:02 -04:00
7c603e6928 Blender source doesn't yet permit modernize-use-override 2021-03-14 13:57:11 -04:00
eb16cebee4 Current blender source hasn't modernized all string literals. 2021-03-14 13:47:57 -04:00
bee5c9720d Merge branch 'master' into soc-2020-io-performance 2021-03-14 13:37:08 -04:00
94dac497b2 Get branch into compling state again.
There are some changes in Materials in the exporter that make compiling
the importer fail. I fixed one and several others are just ifdef'd out
for now so that the branch compiles. TODO to fix the rest.
2021-02-23 11:03:29 -05:00
8abffed35a Merge branch 'master' into soc-2020-io-performance 2021-02-23 09:43:14 -05:00
3ce42cb959 Cleanup 2021-02-22 23:33:45 +05:30
9abc78c3f2 Writer: some tests 2021-02-22 23:33:40 +05:30
a9cf404223 Cleanup: fix typos 2021-02-22 23:33:34 +05:30
29d5149843 Tests: Cleanup 2021-02-22 23:33:13 +05:30
30def30420 Cleanup 2020-12-12 20:47:53 +05:30
10c8b88cf1 Merge branch 'master' into soc-2020-io-performance 2020-12-12 02:35:47 +05:30
5280200275 Exporter: Cleanup: clang-format, comment 2020-12-12 02:35:27 +05:30
ad40a7c3d6 Exporter: create new API for writer.
Isolate formatting options.
Add exceptions instead of three layers of booleans.
Separate header writing from constructor.
2020-12-12 02:35:19 +05:30
d6f0cf048d Exporter: Cleanup: clang-tidy 2020-12-12 02:35:15 +05:30
7b4bb69866 Cleanup: CMake file order and dependencies 2020-12-12 02:35:10 +05:30
fc71aaa487 Exporter: Fix scaling: change scale before applying locrot transform. 2020-12-05 00:28:58 +05:30
c59a5002e2 Merge branch 'master' into soc-2020-io-performance 2020-12-05 00:27:15 +05:30
5c48127877 Importer: Cleanup: const, remove unneeded assignment. 2020-12-02 10:03:25 +05:30
32dad88e8f Importer: Cleanup: single storer; reorder parameters. 2020-12-02 09:56:08 +05:30
7206226ee0 Importer: Use switch-case instead of else-if chain in main code. 2020-12-01 22:46:56 +05:30
b54aa8a77c Importer: make a class OBJStorer, and smaller functions. 2020-12-01 16:49:59 +05:30
7faaaf7fd7 Importer Cleanup: renames, const. 2020-12-01 00:58:44 +05:30
01fa2a239c Importer: remove useless {}s. renames. 2020-11-30 19:37:12 +05:30
6cc5e59b49 Exporter: remove useless {}s. 2020-11-30 19:31:33 +05:30
06d61d459f Merge branch 'master' into soc-2020-io-performance 2020-11-29 17:29:57 +05:30
f4b22244ca Merge branch 'master' into soc-2020-io-performance 2020-11-23 17:11:41 +05:30
df6fe191a9 Remove commented-out code; correct spelling. 2020-11-20 15:05:41 +05:30
d6f4c1f0d3 importer: fix \r issue for file created on windows. 2020-11-19 15:30:21 +05:30
9fc8ec7791 Exporter: Add more OBJCurve tests
File {F9302561}
2020-11-16 20:01:59 +05:30
65b7d952ff Tests: Cleanup: renames, comments. 2020-11-16 20:00:03 +05:30
e5a97df7b5 Cleanup tests: move default to header, add failures. 2020-11-16 18:43:41 +05:30
4b40cce671 Cleanup: rename, comments. 2020-11-16 18:43:16 +05:30
19f926f593 Importer: Cleanup: Spellings 2020-11-16 02:54:30 +05:30
ae8b87e757 Merge branch 'master' into soc-2020-io-performance 2020-11-16 02:28:59 +05:30
7859e3c280 Cleanup: renames, comments. 2020-11-14 18:41:55 +05:30
43c1e37d45 Merge branch 'master' into soc-2020-io-performance 2020-11-14 13:10:45 +05:30
d1492ebb6d cleanup: move defaults to header file; renaming. 2020-11-14 04:11:04 +05:30
36904f01a6 Exporter: add OBJCurves tests.
Corresponds to file https://developer.blender.org/F9278970 which
should be dropped at `io_tests/blend_scene/all_curves_2_92.blend`.
2020-11-14 04:10:21 +05:30
44629501fe Cleanup: renames. 2020-11-13 14:07:54 +05:30
533964ca7d cleanup: remove unneeded fixture; remove magic number. 2020-11-11 20:32:16 +05:30
1c3d4ace05 Merge branch 'master' into soc-2020-io-performance 2020-11-11 20:07:06 +05:30
57ae71dbf3 Exporter: start adding tests.
Corresponds to file https://developer.blender.org/F9260238

It should be put in "io_tests/blend_scene/all_objects_2_92.blend"
directory.
2020-11-11 19:56:28 +05:30
6ca3ca23de Cmake: add initial GTest support, placeholder file. 2020-11-11 19:53:17 +05:30
25bac75fae Exporter: Cleanup: minor rename 2020-11-11 14:42:21 +05:30
bc52869756 Cleanup: const, renames, remove single use variables. 2020-11-10 20:06:41 +05:30
347a12ba4f Merge branch 'master' into soc-2020-io-performance 2020-11-09 21:32:28 +05:30
1223782976 Cleanup: importer: const, range-based for-loops, etc. 2020-11-08 19:01:54 +05:30
30cc8b8567 Cleanup: exporter: const, clang-tidy. 2020-11-08 18:42:39 +05:30
9df5b84b1b Cleanup: exporter: use range-based for loops. 2020-11-08 18:39:40 +05:30
60b5a8ab62 Merge branch 'master' into soc-2020-io-performance 2020-11-08 15:02:13 +05:30
8a785ee54f Cleanup: Clang-format. 2020-11-07 18:37:57 +05:30
b046126ac5 Noise: fix uninitialized variable warning.
Mistake in 74188e6502
2020-11-07 18:34:57 +05:30
5d3533f4d5 Cleanup: use std::make_unique instead of new. 2020-11-07 18:33:17 +05:30
4e8c2cfd1c Cleanup: Importer: NULL to nullptr. 2020-11-07 18:32:04 +05:30
0bfa68c901 Cleanup: Exporter: NULL to nullptr, mismatching parameter name. 2020-11-07 18:26:09 +05:30
d58b2c258b Cleanup: Clang-tidy else-after-return 2020-11-07 18:24:56 +05:30
f830f7cdef Cleanup: Clang-tidy, modernize-concat-nested-namespaces 2020-11-07 18:17:12 +05:30
4db25c833a Merge branch 'master' into soc-2020-io-performance 2020-11-07 15:19:42 +05:30
16c51ce148 Merge branch 'master' into soc-2020-io-performance 2020-11-07 12:25:09 +05:30
af2161f901 Experiment: Remove return value arguments. rename face to poly/polygon everywhere. 2020-11-04 00:24:25 +05:30
5b3c7458e9 Exporter: Complete TODO about exiting edit mode gracefully. 2020-11-03 15:16:26 +05:30
2761a59387 Merge branch 'master' into soc-2020-io-performance 2020-11-03 15:01:24 +05:30
9f14881e61 Cleanup: fix function names 2020-10-28 11:16:39 +05:30
3f94db1af2 Merge branch 'master' into soc-2020-io-performance 2020-10-26 17:31:50 +05:30
53e53c1123 Cleanup: clang-format 2020-10-19 01:43:16 +05:30
447d2b5b12 main exporter file: create smaller functions. 2020-10-19 01:43:08 +05:30
3d12e92200 Cleanup: Remove unused function 2020-10-19 01:43:04 +05:30
d0bb2c6277 Writers: use NonMovable, and NonCopyable. 2020-10-19 01:42:57 +05:30
9554e93efd Cleanup: edit comments as per policy, function and variable names. 2020-10-19 01:42:49 +05:30
f52023a85c clang-tidy mismatched or missing arg names; else-after-return.
No functional change.
2020-10-19 01:42:44 +05:30
f5d986822d Merge branch 'master' into soc-2020-io-performance 2020-10-19 01:42:13 +05:30
7f854c4bc4 Merge branch 'master' into soc-2020-io-performance 2020-10-15 16:14:44 +05:30
2ace7521e3 Merge branch 'master' into soc-2020-io-performance 2020-10-15 14:57:05 +05:30
670c103f10 Move files to exporter and importer folder. 2020-10-07 00:10:47 +05:30
183c4af692 Cleanup: edit comments, spellings. 2020-10-07 00:06:12 +05:30
85f809ca39 Remove writer's destructor to .cc file 2020-10-07 00:06:06 +05:30
f1a60130bd Move normal indices update near normal writer code.
Remove `per_object_tot_normals_` from member variable.
2020-10-07 00:06:00 +05:30
b3f08db450 Merge branch 'master' into soc-2020-io-performance 2020-10-06 14:39:48 +05:30
4f041c98fc Merge branch 'master' into soc-2020-io-performance 2020-10-05 17:54:15 +05:30
0af469d131 Merge branch 'master' into soc-2020-io-performance 2020-10-02 16:33:14 +05:30
4c8253a966 Cleanup: Fix typo in comments. 2020-09-29 22:43:47 +05:30
9c622b9223 Merge branch 'master' into soc-2020-io-performance 2020-09-29 22:19:09 +05:30
1a658978e5 Cleanup: Edit comments, remove unused headers. 2020-09-29 22:18:10 +05:30
c2305201bc Remove the need to apply modifiers to use render properties. 2020-09-29 22:18:05 +05:30
61ad01ec0c Cleanup: edit comments 2020-09-29 22:18:01 +05:30
f0de7b58bf Rename curv_num to control_points 2020-09-29 22:17:56 +05:30
dbd7c31f86 Add more const default variables. 2020-09-29 22:17:50 +05:30
ac2b609e4e Fix UI: don't edit frame numbers if animation is disabled. 2020-09-29 22:17:44 +05:30
d5b0467732 Use fputs instead of fprintf for non-formatted strings. 2020-09-29 22:17:32 +05:30
84d32cb5c5 Cleanup: Remove unneeded headers.
No functional change.
2020-09-21 17:04:48 +05:30
55bc6ac173 Merge branch 'master' into soc-2020-io-performance 2020-09-21 16:52:00 +05:30
9e04dc90c7 Cleanup: clang format. 2020-09-21 16:36:01 +05:30
78bbdbd931 Add emission strength to MTL importer.
Ref rBAec4ad081e564
2020-09-21 16:36:01 +05:30
0444cabb41 Keep axes defaults same as python importer. 2020-09-21 16:36:01 +05:30
33bd98a97b Remove duplicate MTLMaterial, tex_map_XX. clean-up headers. 2020-09-21 16:36:01 +05:30
8b316eb7a8 Mesh: remove unused function, wrong assert, {} etc. 2020-09-21 15:59:25 +05:30
bc2a8f01ba Material: add emission strength's effect on Ke (color)
Ref {rBAec4ad081e564}
2020-09-21 15:59:25 +05:30
aaa4a544ae Material: add comments, use explicit float3 initialisation. 2020-09-21 15:56:45 +05:30
ef8b8f4030 Material: style guide, Class rearrangement. 2020-09-21 15:53:24 +05:30
2e16d14cee Writer: add object group writer member function 2020-09-21 15:46:46 +05:30
942039244e Nurbs: remove depsgraph from class, minor cleanup. 2020-09-21 15:46:45 +05:30
5ab177be32 Replace uint and short with int and int16_t 2020-09-21 15:46:45 +05:30
6570780a79 Cleanup: exporter main: use const, style guide. 2020-09-21 15:46:45 +05:30
e96dd70fac Cleanup: use const, edit comments, reorder includes.
No functional change.
2020-09-21 15:46:45 +05:30
b6b4aa99ff Change axes transform defaults to match old behavior. 2020-09-21 15:46:45 +05:30
ded6377ea0 Write empty usemtl if a polygon has no material. 2020-09-21 15:46:45 +05:30
3e544770f6 Merge branch 'master' into soc-2020-io-performance 2020-09-21 15:45:04 +05:30
8d5101ce1d MTLWriter: overwrite the file instead of appending to it. 2020-09-16 16:30:06 +05:30
8ce2fdee8b Prepend importer_ to mesh_utils; parser_ to string_utils. 2020-09-16 16:20:29 +05:30
9e15728380 Move MTLMaterial & float3_to_string to exporter files.
Keeping the branch in sync with D8754 and D8753.
2020-09-16 16:09:02 +05:30
c1c1e3ea88 Merge branch 'exporter_n' into soc-2020-io-performance 2020-09-16 15:57:09 +05:30
d1cc8c4712 Move vertex index Vector resize to OBJMesh. 2020-09-16 15:17:59 +05:30
fb75c957de Corrections: correct wrong assert, remove cast. 2020-09-16 15:17:07 +05:30
83e0db031a Simplify material group calculation. 2020-09-16 15:15:28 +05:30
9ea4a50fee Simplify vertex group calculation. 2020-09-16 15:13:53 +05:30
a45112a9ec Add smooth group constants, use "0" instead of "off". 2020-09-16 15:00:31 +05:30
dfeabd0032 Add polygon element writer selector to OBJWriter. 2020-09-16 14:56:54 +05:30
0f99e37602 Add polygon normal indices calculation method to OBJMesh. 2020-09-16 14:38:22 +05:30
1d19c96a1f Remove depsgraph member variable; use const variables. 2020-09-16 14:29:19 +05:30
641f859f88 Material: Disambiguate mat_nr + 1. 2020-09-16 14:27:09 +05:30
5e54c8a0d5 Add method to free mesh; disambiguate triangulate method. 2020-09-16 14:24:07 +05:30
047189baf0 Create MTLWriter object early, add methods for filename and file status. 2020-09-16 12:09:07 +05:30
8b3f87f9f7 Cleanup: rename functions, use const, edit comments. 2020-09-16 11:58:30 +05:30
b7689c0083 Create IndexOffsets struct instead of array[3].
Make OBJ indices one-based in Writer, not in Object processor.
2020-09-16 11:46:29 +05:30
b99246c613 Early return in a few places.
Fix two consecutive group name `g ...`  lines by adding an `else`.
2020-09-16 11:33:24 +05:30
e3e01ae8a2 Remove UNUSED macro from poly elements writers. 2020-09-16 11:28:38 +05:30
82b4639477 Break MTLWriter::append_materials into two functions.
Silence unused lambda warning in release build.
2020-09-16 11:26:50 +05:30
c5f984d96c Log errors in fopen and fclose. 2020-09-16 11:20:32 +05:30
b136e11526 CMake: Keep unconditional code together. 2020-09-16 11:13:59 +05:30
52d9d6cd21 Review update: Fix exporter UI issues:
Simplify frame correction logic.

Not reset bitflags checkbox.

Use INT_MIN, not -INT_MAX.

Simplify tool-tips.
2020-09-16 11:12:50 +05:30
d67476a83f Review update: fix exporter UI. 2020-09-15 01:15:09 +05:30
1a0909b15d Export loop normals not vertex normals. 2020-09-15 01:15:09 +05:30
2ce3ffb306 Don't write default scale and translation.
Also fix debug warning of material not found.

Add asserts to catch unchanged values.
2020-09-15 01:15:09 +05:30
159a6bc537 Minor fixes, use scoped_timer for export too. 2020-09-15 01:15:09 +05:30
30e48c58ba Cleanup: comments and rna warning. 2020-09-15 01:15:09 +05:30
5c3ec6245e Fix UI tooltips, layout, frame setting.
Fix NodetreeRef crashing due to null nodetree.
2020-09-15 01:15:09 +05:30
a742bede2b Move uv_indices in OBJMesh. Save some memory.
Since OBJMesh has dynamically allocated space in smooth groups
and uv_indices (which can be a lot), destruct objects that have
been written.

Use `const` at some places.
2020-09-15 01:15:09 +05:30
3bffab7a07 Hide Nurb from Writer. Rename OBJNurbs as per object type. 2020-09-15 01:15:09 +05:30
bc78649c01 Remove export_params_ from OBJNurbs. 2020-09-15 01:15:09 +05:30
eaf54f9eb7 Remove export_params_ member from OBJMesh. 2020-09-15 01:15:09 +05:30
31d6df6d35 Not expose MPoly to writer. Write normals correctly
Write vertex normals only of smooth shaded polygons, otherwise
write face normals.

Keep normal indices in writer only since OBJMesh doesn't need to know
about export parameters. (will remove more such items from OBJMesh
later on.)

Keep MPoly related operations inside OBJMesh only & don't expose it to
Writer.
2020-09-15 01:15:09 +05:30
f4c70d7c9f Review update: use float3, std::optional, c_str()
split nurbs function into two.

Add material name append if material groups are specified.
2020-09-15 01:15:09 +05:30
fd529c01c9 fix smooth groups calculation 2020-09-15 01:15:09 +05:30
0af083449e Review update: fix UI text; add comments.
Variable type change for `forward_axis` and `up_axis`.
2020-09-15 01:15:09 +05:30
448dc2da2b Add exporter specific code.
Files like `io_obj.c` which contain both importer and exporter code
are also here.

Differential Revision: https://developer.blender.org/D8754
2020-09-15 01:15:09 +05:30
67a147d2c4 Merge branch 'master' into soc-2020-io-performance 2020-09-14 20:54:38 +05:30
6a5b147a9f Review update: fix exporter UI. 2020-09-04 17:28:24 +05:30
8279501af5 Change bsdf values as per illum values.
Set defaults of MTLMaterial that is useful for importer.
For exporter, asserts have been added.
2020-09-04 17:07:50 +05:30
b96aec3691 Export loop normals not vertex normals. 2020-09-04 16:58:45 +05:30
5117948285 Don't write default scale and translation.
Also fix debug warning of material not found.

Add asserts to catch unchanged values.
2020-09-04 16:56:07 +05:30
6c8f6d5c44 Add float3 to string function to utilities.
Makes the MTL writer a bit compact. That change to be committed soon.
2020-09-04 13:33:03 +05:30
6f7d03b643 Move string utility functions in one file.
No functional change. Only renaming, and adding a few const.
2020-09-04 13:33:03 +05:30
d0e91f78fa Rename vert_treplet to vert_index_mlen; add comments 2020-09-03 16:09:31 +05:30
83ac5a0f5f Minor fixes, use scoped_timer for export too. 2020-09-03 16:08:47 +05:30
85b1234107 Minor fixes, clang-tidy warnings. 2020-09-03 16:07:21 +05:30
21a097f952 Revert renaming mesh_utils to utils.
No functional change.
2020-09-03 14:30:22 +05:30
6b62935e89 Fix importing multiple material for one object.
Also add sphere type reflection to Metallic socket.

Remove double inline warnings, and also trust compiler to inline
what it deems fit.
2020-09-03 00:10:45 +05:30
24a9729921 Cleanup: comments and rna warning. 2020-09-02 23:47:22 +05:30
008eb7af41 Fix build errors on MSVC. 2020-09-02 14:30:01 +05:30
3c4a67c575 Fix UI tooltips, layout, frame setting.
Fix NodetreeRef crashing due to null nodetree.
2020-09-02 14:29:28 +05:30
54f15a66a0 Move uv_indices in OBJMesh. Save some memory.
Since OBJMesh has dynamically allocated space in smooth groups
and uv_indices (which can be a lot), destruct objects that have
been written.

Use `const` at some places.
2020-09-01 17:49:16 +05:30
0ba0c3653b Use std::array, std::min, remove debug function.
Review update for D8753.
2020-09-01 15:13:46 +05:30
40a2caca86 Hide Nurb from Writer. Rename OBJNurbs as per object type. 2020-09-01 14:07:38 +05:30
be046b01d1 Remove export_params_ from OBJNurbs. 2020-09-01 04:39:45 +05:30
7a5d794862 Remove export_params_ member from OBJMesh. 2020-09-01 03:33:42 +05:30
9445be11fb Not expose MPoly to writer. Write normals correctly
Write vertex normals only of smooth shaded polygons, otherwise
write face normals.

Keep normal indices in writer only since OBJMesh doesn't need to know
about export parameters. (will remove more such items from OBJMesh
later on.)

Keep MPoly related operations inside OBJMesh only & don't expose it to
Writer.
2020-09-01 03:33:05 +05:30
0b9f41f4a0 Review update: use float3, std::optional, c_str()
split nurbs function into two.

Add material name append if material groups are specified.
2020-09-01 01:41:34 +05:30
a6ff8534f2 fix smooth groups calculation 2020-09-01 01:39:44 +05:30
c666a4c03c Review update: fix UI text; add comments.
Variable type change for `forward_axis` and `up_axis`.
2020-08-31 22:08:44 +05:30
e715c482cc Rename File menu item text. 2020-08-30 20:26:42 +05:30
c239d5f3b0 Rename files to shorten the name, remove ex/im.
Two letter abbreviations were not deemed good.
2020-08-30 20:19:27 +05:30
8290d2d15c Cleanup: Add description, rename conflicting name
max/min are also functions, so better not keep them as variables.
No functional change.
2020-08-30 19:31:39 +05:30
46ff12a5d2 Add transform options in the importer UI. 2020-08-30 19:21:32 +05:30
5be5f3b4bb Fix minor bugs in logging. 2020-08-30 18:58:18 +05:30
387c58847d Merge branch 'master' into soc-2020-io-performance 2020-08-29 22:39:03 +05:30
80abfd7d55 Fix face element import: index out of range crash 2020-08-28 19:38:45 +05:30
fc816d358e Fix build error after merge; add logging.
Fix typo in the {rB0aeb338d1952725cfb8}: really turn off tessellation
this time.
2020-08-28 17:39:06 +05:30
87f407d679 Merge branch 'master' into soc-2020-io-performance 2020-08-28 17:00:33 +05:30
0aeb338d19 Use unique_ptr for MTLMaterials Map.
Also, early return in `tessellate_polygons`. Turn off tessellation
for a while till valid/invalid options are added in parsing.
2020-08-28 16:52:33 +05:30
9ac8def136 Fix ngon_tessellate filling the holes.
Fix several issues from the commit {rBefab0bc704ece3a}.
Previously, `ngon_tessellate` was filling the holes instead of
triangulating around them by using the "triangle-fan" method.

Now it triangulates around the holes and doesn't fill them.
2020-08-28 03:13:36 +05:30
a662ddd76a Read multiple lines with \\n line breaks. 2020-08-24 00:39:01 +05:30
8c6a06e730 Cleanup: Mark unused variables. 2020-08-23 22:06:38 +05:30
32fe286919 Silence mesh_validate verbose warnings. 2020-08-23 22:05:38 +05:30
b22b962ca2 Silence some warnings; minor changes.
Since `map_bump_strength` was changed to have a
leading space, length should be increased too.

Pass by reference to avoid copy.

Release resources if not moved by the owner in Mesh and
Curve creation code.
2020-08-22 12:40:30 +05:30
6cd247c277 Cleanup: add r_ prefix.
No functional change.

r_offsets -> r_offset.
2020-08-22 12:33:21 +05:30
73454388fb Use blender::StringRef instead of std::string_view.
Context:
{rB9616e2ef78f0}
{rBc521b69ffb68}

In MTL parser, `Vector.first_index_of_try` result was being compared
with `string::npos` which gave correct results since `npos` is -1,
but is wrong semantically. So fix it.
2020-08-22 01:21:10 +05:30
ae122533e7 Use remove_prefix for easy transition to StringRef.
The relevant search functionality was added to StringRef in
{rBc521b69ffb68}
2020-08-22 00:44:43 +05:30
4777a6a54a Move object creation code from constructor.
Also add destructors with assert in case caller doesn't
own the object.

Rename `blender_object_` to `mesh_object_` to be like
`curve_object_`.
2020-08-21 23:06:55 +05:30
d514de67e6 Cleanup: Comments, clang-format. 2020-08-21 22:15:33 +05:30
edd5307e9e Remove unused uv vertex index offset. 2020-08-21 21:31:15 +05:30
10e3f23ba3 Edit FaceElem list outside polygon creating code.
`new_faces` was a lie after it was extended. Better is all_faces.
2020-08-21 21:06:40 +05:30
e76ab12454 Add newly creates faces to imported faces and dissolve edges.
The code so far imports triangles, but also fills holes.
Committing  since it doesn't crash.

test file:

```
o plane_with_hole
v 2 2 0
v 2 -2 0
v -2 -2 0
v -2 2 0

v 1 1 0
v 1 -1 0
v -1 -1 0
v -1 1 0

f 4 1 2 3 4 8 5 6 7 8 4
```
2020-08-21 16:13:02 +05:30
5b0cb5bbbe Fix tessellate crash due to wrong Vector size.
Also fix crash due to out of bound vertex indices.
2020-08-21 16:12:50 +05:30
efab0bc704 Commit as it is ngon-tessellate.
The code is totally untested and will very likely crash.
2020-08-21 15:46:02 +05:30
69270c7800 Cleanup: use const, remove single use variables. 2020-08-18 16:02:02 +05:30
0c7801fa2b Fix overlapping nodes in the nodetree.
Fix filepath being wrong in cases like `-bm 0 filepath`.

Use fallback value in out of range exception.
2020-08-18 16:01:31 +05:30
3f8a8d1757 Silence normal being zero warnings in mesh validate. 2020-08-18 14:30:32 +05:30
c583afd60b Parse vertex normals: vn lines.
Still need to understand how it translates to the
normals seen in the viewport.
2020-08-18 01:30:39 +05:30
0479dc410f Decouple getting smooth groups from export parameters 2020-08-18 01:28:59 +05:30
4923087d25 Export vertex normals if smooth shading is enabled.
`s on/off` line will not be written if that is not checked in the
exporter parameters.
2020-08-17 15:30:44 +05:30
491bd8b8bb Move file open errors to constructors. 2020-08-17 12:16:20 +05:30
c815ba0a3c Add a public member function in MaterialWrap. 2020-08-17 12:15:33 +05:30
c5e0e82f68 Cleanup : clang format; cout > cerr 2020-08-16 19:38:43 +05:30
a732abd99b Clean up: use const, header cleanup.
Also fix build issues that the last two commits introduced.

https://github.com/include-what-you-use/include-what-you-use
2020-08-16 19:32:52 +05:30
99ededd947 Skip parsing extra texture map options, move MTLWriter from Objects code. 2020-08-16 19:31:16 +05:30
ae1c5f16cb Move MTL writer code near OBJ writer code. 2020-08-16 19:28:16 +05:30
d882a63e98 Apply axes transform to exportable Nurbs. 2020-08-14 14:26:41 +05:30
69f70829d2 Fix usemtl line not being written.
`==` → `!=` : the bug.

`totface` → `totpoly`: it was a mistake.

Rest is cleanup.
2020-08-14 01:56:54 +05:30
ba0d376e26 Use function pointer; remove redundant variables. 2020-08-14 01:29:11 +05:30
d9cdfba21e Export packed image file names, not paths. 2020-08-13 23:39:21 +05:30
a725b6f7ac Use const; move constructors to implementation file. 2020-08-13 21:09:53 +05:30
97aeaf8dde Merge branch 'master' into soc-2020-io-performance 2020-08-13 18:18:40 +05:30
d5866e8d74 Cleanup: renaming & comments. 2020-08-13 18:07:58 +05:30
606b0d7da3 Fix several material import bugs, remove wrong assert.
Images were not being loaded since the parent directory was
missing in the path. So add it in tex_map_XX.
2020-08-13 18:00:56 +05:30
d2a798d29f Merge branch 'master' into soc-2020-io-performance 2020-08-12 02:52:04 +05:30
c010cf5814 Fix loose edge export & import, and offsets. 2020-08-12 02:47:11 +05:30
e0c0b8ffd1 Fix vertex offsets for MEdges and MLoops. 2020-08-11 20:34:56 +05:30
bb2eca07a6 Catch out of range exceptions for stoi/stof. 2020-08-11 03:20:59 +05:30
b378f84f7c Add vertex offset due to Curves' vertices also. 2020-08-11 02:53:01 +05:30
009b37719d Fix new Geometry creation with empty initial object. 2020-08-11 02:52:10 +05:30
82eff7a025 Use const& instead of pointers in Mesh class. 2020-08-11 00:05:00 +05:30
d062c712e2 Correct geometry creation code with Curves.
Also make `Geometry` and `GlobalVertices` members of
`CurveFromGeometry` as `const &` to match with
`MeshFromGeometry`.
2020-08-10 23:58:21 +05:30
a0f21e47ae Merge branch 'master' into soc-2020-io-performance 2020-08-10 21:06:18 +05:30
d2fcc7b48c Use FILE_MAX instead of PATH_MAX
Also, allow more vertices data points to be in int
strings.
2020-08-10 21:01:21 +05:30
6ee696e5bf Accept 3D UV vertex coordinates
The third one is still discarded.
2020-08-10 15:25:42 +05:30
9b37f94324 Create new Geometry instance at the start.
Since the following is a valid OBJ file, a new `Geometry` instance
is always required.

```
v 1.0 2.0
```
2020-08-10 15:20:19 +05:30
7f2893848e Move index offset from parsing to Object creation
The only place where the information needed about how many vertices
have been occupied by other objects is `mloop->v` since `v` has to
be in the range from zero to total vertices _in a_ mesh.

All other indices (edge, UV, normal) work best when they're parsed
the way they're written and the corresponding data from the global
list is read directly.

So instead of modifying every index just to keep `mloop->v` happy,
use a `Map<int, int>` for storing vertex indices. This reduces chances
of error greatly and avoid "indices to indices to coordinates".

`Vector` would've been very slow it being unsorted & lookups being done
for _all_ `mloop`s. `Map` gives no drop in performance.

UV vertices from `Geometry` have been removed since `FaceCorner`s store
a direct index indexing into the Global list of UV coordinates.
2020-08-09 19:52:27 +05:30
8598993157 Fix wrong material exporter assert added recently.
Since Alpha in RGBA is not written to the MTL file, callers
use array of length 3.
2020-08-09 17:32:23 +05:30
9616e2ef78 Use std::string_view for string splitting.
While being slightly more error prone, this change
gives a speedup of 1.9 s vs 1.2 s on a 23.3 MB 8-level
subdivided cube OBJ file. [1] The biggest time sink earlier
was splitting strings into smaller ones. Now it is `std::stof`
about which nothing much can be done.

`StringRef` has been completely removed to avoid
casts between `std::string_view::size_type` & `int64_t`
that `StringRef::size()` gives. Also, `find_{first/last}_{not/}_of`
and `substr` have been used a lot, which are missing in `StrinRef`.

The change also removes `\r\n` carriage return line breaks
while cleaning up the string.

Use `blender::StringRefNull`  where null-terminated strings
are present.

`fprintf` has been replaced with `std::cerr` since
`std::string_view::data()` may give a pointer to a non
null terminated buffer. Also, error logging is not performance
-critical. [2]

[1] See week 7 reports for breakdown on the older timings.
https://wiki.blender.org/wiki/User:Ankitm/GSoC_2020_Daily_Reports#Week_7
[2] https://en.cppreference.com/w/cpp/string/basic_string_view/data
2020-08-09 01:12:36 +05:30
6e419e95e7 Remove deleted header from CMakeLists.txt 2020-08-08 02:58:31 +05:30
0a339bb5e7 Use pragma once 2020-08-08 00:28:09 +05:30
5fa1e0eb38 Merge branch 'master' into soc-2020-io-performance 2020-08-07 23:59:55 +05:30
3cff0de435 Fix crash due to OBJ files with no o name line
In some exporters, the group serves as the object name.

Also add some asserts.

Rename `list_of_objects` to `all_geometries`.
2020-08-07 19:46:21 +05:30
27cebf309f Merge branch 'master' into soc-2020-io-performance 2020-08-06 20:46:54 +05:30
06336941b3 Cleanup: Renaming, comments. 2020-08-06 20:07:13 +05:30
2c20b379f9 Don't add null Image* to the p-BSDF node. 2020-08-06 19:03:23 +05:30
d081bf97df Clean-up: Warnings, renaming variables. 2020-08-06 16:41:39 +05:30
befe950f18 Avoid .data() while creating a std::string from StringRef. 2020-08-06 16:28:45 +05:30
1b1727ea6a Remove OBJ prefix from containers.
That is indicated by the namespace itself..
2020-08-06 16:27:56 +05:30
d660455882 Cleanup: Remove Raw* terminology, use geom_type.
After discussion with the mentors [1], the terminology to use
Raw has been removed & `Geometry` is the new name for
"raw objects". eGeometryType has also been added to remove
confusion with OB_MESH etc.

`std::unique_ptr<T > *a` has been replaced with `T *a`.

Naming changes:

`curr_ob` -> `current_geometry` in the parser &
`geometry` in the `Mesh` & `Curve` creators.

`mesh_from_raw_` -> `blender_mesh_`. Similar for curve.

[1]: https://docs.google.com/document/d/17Uzl47OljjoKgaMbukiLHUVGQP220lPTmPS-atb65mw/
2020-08-06 16:08:26 +05:30
8339dd6647 Replace std::string_view with StringRef.
Add comments.
2020-08-06 15:04:36 +05:30
d6f9400417 Merge branch 'master' into soc-2020-io-performance 2020-08-06 01:41:16 +05:30
928736b173 Replace enum and its functions with Map for maps
The enum approach was adding lots of repetitive functions
and switch-case blocks.  Use `blender::Map` instead.

Also fix nodes overlapping.

Connect Texture nodes with correct sockets of p-BSDF.
2020-08-06 01:39:14 +05:30
9b8f2042b0 Add created material to the object. 2020-08-05 23:09:08 +05:30
f9348be84f Create image for image texture node.
Also keep pointer to MTLMaterial in the class itself.
2020-08-05 20:27:20 +05:30
b8e4d4c6da Cleanup: clang-tidy warnings
else after return.

"..." can be made a static function instead of member function.
2020-08-05 20:24:04 +05:30
d5ad01edf3 Fix bsdf_ being null; asserts for SOCK_RGBA
Use  `ntreeType_Shader->idname` instead of manually entering the
ID to avoid mistakes (which I did!
`ShaderNodeTree` vs `ShaderNodetree`).

Add assert in case caller doesn't own the `nodetree_`.

Fix passing `float a, int b` to `Span` instead
of `float *, size_t size`.
2020-08-04 21:27:29 +05:30
64ff38a7a7 Use Map's lookup instead of direct pointer to MTLMaterial
A direct pointer to a struct no longer points to the internal struct
of the `Map`, it seems. So use lookup which returns the reference too.
2020-08-04 19:30:38 +05:30
722a793b74 MTL: use mtllib for importing MTL files.
Create a utility function for splitting lines into the first words
& the rest of it.
2020-08-04 18:53:17 +05:30
8e58bd0996 MTL: release shader_output_ node instead of bsdf_. 2020-08-04 16:35:53 +05:30
a26657cdb8 Merge branch 'master' into soc-2020-io-performance 2020-08-04 15:57:54 +05:30
c401d8a0ae Initial material creation support. 2020-08-04 15:56:33 +05:30
48d3582196 Cleanup: Use MutableSpan, add asserts
Remove passing of unnecessary variables.

Limit scope of `tex_image_filepath`.
2020-08-04 15:54:45 +05:30
9433124290 Merge branch 'master' into soc-2020-io-performance 2020-07-31 16:36:55 +05:30
7b77d88275 Merge branch 'master' into soc-2020-io-performance 2020-07-30 18:26:03 +05:30
19145856ba Add MTL parser. 2020-07-30 18:24:52 +05:30
c5bd1631f6 Add fallback value in string to number conversions
Remove remnant `stringstream s_line` which was used with `>>`
operator for storing floats and ints. That has been replaced
with `stof`/ `stoi`.

Limit the scope of MTL exporter's tex_node pointer.
2020-07-30 18:07:13 +05:30
a59bbede21 Merge branch 'master' into soc-2020-io-performance 2020-07-27 14:31:03 +05:30
78f29f6c0a Fix build error due to forward enum declaration
In full build, the following error happens. So fix it by including the
definition file before the forward declaration one.

```
In file included from source/blender/io/collada/SkinInfo.cpp:40:
source/blender/blenkernel/BKE_object_deform.h:62:6: error:
 enumeration previously declared with nonfixed underlying type
enum eVGroupSelect
     ^
In file included from source/blender/io/collada/SkinInfo.cpp:36:
source/blender/makesdna/DNA_scene_types.h:2099:14: note:
previous declaration is here
typedef enum eVGroupSelect {
             ^
1 error generated.
```
2020-07-27 13:38:08 +05:30
b33a4592a3 Use VectorSet instead of vector for def_group.
The lookups are now faster and insertion order is also
 retained since no item is removed from the `VectorSet`.

Also fix a leak that happened due to mishandling of the pointer
to the allocated memory of `MDeformWeight`.

Remove potentially confusing loop index that is used with
`tot_loop_idx`.
2020-07-25 13:20:34 +05:30
ffaa1df439 Cleanup: silence clang-tidy warnings; comments.
Use static_cast wherever applicable.

Pass struct by reference.

Remove unnecessary const qualifiers for primitive types.

Make some member functions static instead.

Remove intermediate non-owning pointers to new class instances.

Remove else in else-after-return.

Initialise variables at definition.
2020-07-25 00:05:45 +05:30
37467ec5e9 Keep vertex deform group names and def_nr in sync
I realised later on that Map is unordered, so the order of insertion,
on which def_nr depends, is not retained. So a Vector of strings
has been used & the index of the string-of-interest is the def_nr.

Unfortunately, it will be slow on large number of vertex groups.
2020-07-24 21:02:29 +05:30
5898f6ef1f Merge branch 'master' into soc-2020-io-performance 2020-07-24 03:34:53 +05:30
af278ce58b Fix curve not being visible in Object mode.
Thanks to Howard Trickey(@howardt) for finding the fix!
2020-07-24 03:33:16 +05:30
25526821a4 Support importing vertex deform groups.
Remove unnecessary casting in exporter's deform group related code.

The fix in `BKE_object_deform.h` is temporary, until D8378
is committed to master.
2020-07-24 03:15:21 +05:30
4ec4f5b309 Fix build errors due to wrong auto to type changes
They were the last changes I made and didn't build afterwards.
2020-07-24 00:54:06 +05:30
ed8c902a6a Cleanup: Fix typo in comments. 2020-07-23 21:38:09 +05:30
2bb56c83fa Move Object creation code in Mesh creation class.
This is required for setting deform groups which are a part of `Object` struct.

Also rename NurbsElem > OBJNurbsElem.

Remove forward declarations from *nurbs.hh & *mesh.hh files
since the cyclic dependency with *object.hh is removed.

Remove auto.
2020-07-23 21:35:16 +05:30
4716660591 Rename exportable_meshes to exportable_as_mesh
This change is to make it more clear that this is not a list of
Blender meshes, but objects that are being exported in Mesh form.
Same for NURBS curve.
2020-07-23 16:11:34 +05:30
97aa9d44fa Refactor: Conform to style guide, add documentation.
Rename classes OBJImporter > OBJParser, OBJParentCollection >
OBJmportCollection.

Move OBJRawObject items to private access & use getters. Parser class
is set to its friend since it edits the Raw object heavily.

Break OBJMeshFromRaw and OBJCurveFromRaw into smaller functions with
one responsibility.

Add comments wherever required.

Use int instead of uint.

Remove typedef, use Span.
2020-07-23 15:58:43 +05:30
cab598f2b2 Set active NURBS of a curve; set object group.
Correction for `CU_NURB_ENDPOINT` being a valid `flagu` value , not
`flag`.
2020-07-22 20:50:12 +05:30
31b7a53605 Cleanup: fix grammar in comments. 2020-07-22 14:53:47 +05:30
7bd38c2776 Fix build errors: avoid uint with Vector size() function.
https://wiki.blender.org/wiki/Style_Guide/C_Cpp#Integer_Types
2020-07-22 03:04:53 +05:30
95716b7681 Merge branch 'master' into soc-2020-io-performance 2020-07-22 02:43:56 +05:30
6e21f8c20d Support NURBS curve import & minimal groups support.
There's a problem though: the curve's vertices appear in edit mode
just fine, but the black line doesn't appear in object mode. But the code
so far should've been committed.

Also, some semantics have been made consistent which needed mesh
related functions/ members to be edited:
- `add_object_to_parent` for mesh is now similar to that of curves.
- `OB_MESH` is the default type of a raw object.

`copy_string_to_int` is also added to support multiple `int` values.
2020-07-22 02:40:47 +05:30
9cb750ba66 Cleanup: Remove unused functions and headers.
Explicitly specify empty string.
2020-07-22 01:52:21 +05:30
b9718a4795 Refactor: move functions out of Importer class
It got left out of the last commit.
2020-07-21 15:57:59 +05:30
a7f5998550 Refactor: move functions out of Importer class
No functional change
2020-07-21 14:23:18 +05:30
71eadb4b62 Move vertex (and UV vertex) list outside OBJRawObject
Since an OBJ file may contain data like [1], the vertex coordinates
list should be detached from any `OBJRawObject`. So every raw object
keeps track of its vertices by indexing into the global list of coordinates.

[1]
```
o plane
v 0 0 2
v 0 2 0
v 2 0 0
f 1 2 3
# note missing o <name>
v 1 1 0
v 1 0 0
v 0 1 0
v 0 0 0
g curve
cstype bspline
curv 0 1 -1 -2 -3 -4
...
```
2020-07-20 16:56:10 +05:30
5468cc0aae Fix wrong curve indices: missing parentheses. 2020-07-18 23:23:16 +05:30
7139d216f3 Fix build issues after merge: BKE_* -> NOD_*
Also remove some extra paths from CMakeLists.txt.
2020-07-18 15:32:23 +05:30
dce0a628a2 Merge branch 'master' into soc-2020-io-performance 2020-07-18 14:54:24 +05:30
65fd3be1fa Use MutableSpan instead of raw pointers
Since nearly all but one use cases for `copy_string_to_int`
is for single integer, MutableSpan only adds extra
`{&integer ,1}` syntax at the caller. Better use `int &r_dst`.

Also make exception `const`

Add assert before casting `int` to `uint` for MEdge.
2020-07-18 14:50:28 +05:30
5a9b983263 Support meshes with edges with or without polygons. 2020-07-17 23:16:52 +05:30
37d59dbc8c Cleanup: Rename variables, use {} initialisation.
`{}` syntax would help catch when the C++ containers size() function
changes to `int64_t` since initialiser lists don't allow narrowing variables.

Specify that the strings are `_split`.
2020-07-17 22:55:21 +05:30
e33d8f79a9 Merge branch 'master' into soc-2020-io-performance 2020-07-17 11:33:27 +05:30
ef5941f31a Move stoi into a function; Add exception handling
Also add comments for utility functions.

Also make `string` a `const &`; prefix return variables with `r_`.

Use `string::empty()` instead of comparing it with "" (clang-tidy).
2020-07-17 02:13:04 +05:30
fa0daf9a3e Rename: mesh_from_bm_ to mesh_from_ob_ 2020-07-16 21:28:46 +05:30
151e882512 Exporter: Fix UV indices being edited by the writer.
Also revert
{{rBd68899e99a0c}}
2020-07-16 21:08:07 +05:30
fe4c5350c4 Importer: Fix misaligned UV vertices of a mesh
The previous code was not initialising duplicate UV vertices which
created bad shapes in UV editor.

Also use range-loop.

Also since set_uv needs not be edited, mark curr_object const again.
2020-07-16 20:57:40 +05:30
c275b2784b Exporter: Fix freeing invalid pointer in ~OBJMesh 2020-07-16 20:52:26 +05:30
aab8982f9e Merge branch 'master' into soc-2020-io-performance 2020-07-16 16:04:07 +05:30
92be92befe Fix UV crashes and redundant loops in v1//vn1 case. 2020-07-16 16:00:46 +05:30
031c4732f4 Importer: Support smooth shading for polygons. 2020-07-16 15:59:42 +05:30
501ead4bbc Exporter: fix vertex normals not being exported
The smooth group calculation must be done before normals are
written, to decide between vertex normals and face normals.
So moved the smooth group array in the OBJMesh's private members.
2020-07-16 13:15:06 +05:30
81d46ef2bd Null check: Break if no UV layer found.
Also conditionally add an UV layer, not always.
2020-07-15 20:56:05 +05:30
a4a1184ece Add UV vertices to the newly created mesh.
The mesh->mloopuv has the correct vertices, however it hits
the assert at  `(uniform), function GPU_batch_uniform_4fv,`
`blender/source/blender/gpu/intern/gpu_batch.c, line 617.`
if one switches to UV editing workspace.
2020-07-15 20:21:21 +05:30
d68899e99a Exporter: Fix UV vertex indices off by one. 2020-07-15 20:16:57 +05:30
aa9e4b23e5 Cleanup single use variable, comments, formatting
Also, vert_texture.size() was wrongly compared to 1. It's changed to 2.
2020-07-15 14:39:29 +05:30
aacb1f4756 Use stoi and stof instead of >> for faster parsing. 2020-07-15 04:02:51 +05:30
5e9196ed11 Parse UV coordinates and normals.
Adding UV to the mesh is not done yet.
Using `std:stoi` seems to add some benefits, so UVs will be added
to mesh after parser is complete.
2020-07-15 03:12:35 +05:30
4c9b344a5d Merge branch 'master' into soc-2020-io-performance 2020-07-14 19:35:30 +05:30
ec04edfe5c Directly create Mesh without using intermediate BMesh 2020-07-14 19:23:54 +05:30
3cfcf37cea Refactor: Keep mesh_from_raw_object in a class
The function is moved to a class for a self-contained mesh operation.
BMesh operations will not be required without a mesh first, so they
should be kept in a Mesh operation class.

Remove unused variable: bmain

Keep classes ordered as per Style Guide.

Pass object_name to add_object_to_parent since only that is required.
2020-07-14 01:32:16 +05:30
f8d64b396d Fix build error in due to BKE_mesh.h 2020-07-13 15:35:14 +05:30
582bf4397c Rename files: exporter -> ex, importer -> im.
Minor header cleanup.
2020-07-13 15:32:22 +05:30
7582bbd574 Break code into mesh, objects, and reader files. 2020-07-13 15:13:48 +05:30
f7c2fb187d clang format, style: use {} initialization, nullptr. 2020-07-12 22:04:36 +05:30
c4cec5e52c Make custom B/Mesh deleters for clean syntax 2020-07-12 22:03:43 +05:30
7b123fec1c Fix build error 2020-07-12 19:14:26 +05:30
3e30be30ac Merge local branch for importer into soc-2020-io-performance 2020-07-12 19:08:47 +05:30
e3fb4d0dd6 Merge branch 'master' into soc-2020-io-performance 2020-07-12 18:57:01 +05:30
fc58522598 Use custom creators and deleters for Mesh and BMesh
Avoid freeing meshes allocated by Guarded allocated by unique_ptr
2020-07-12 18:54:06 +05:30
d00d2bd308 Mark OBJRawObject const in add_object_to_parent. 2020-07-12 18:48:47 +05:30
e9aa1b92d6 Add add_polygon_from_verts and add_bmvert to OBJBmeshFromRaw 2020-07-11 22:42:53 +05:30
a10942814a Rename OBJMeshToBmesh to OBJRawToBmesh 2020-07-11 22:25:01 +05:30
f4abd34699 Use better memory management for intermediate meshes. 2020-07-11 22:21:00 +05:30
5299b52d7a Removed extra vertices in the new bmesh
Also considering to use `BM_mesh_bm_to_me_for_eval` to avoid
time spent in `BM_mesh_bm_to_me` for mesh data not required for OBJ.
So some commented code is present.
2020-07-11 02:49:41 +05:30
d14811e517 Commit a basic working importer, with some extra-to-be-removed code. 2020-07-10 23:23:08 +05:30
928f5c9b9a Merge branch 'master' into soc-2020-io-performance 2020-07-10 23:22:41 +05:30
550b7cfba0 Merge branch 'master' into soc-2020-io-performance 2020-07-07 23:15:43 +05:30
e517cc0a06 Use short for tot_col: total materials in an object. 2020-07-06 15:56:13 +05:30
a344a1cbe8 Grammar fixes in comments. 2020-07-06 15:38:42 +05:30
d7f9a627f6 Merge branch 'master' into soc-2020-io-performance 2020-07-06 14:21:19 +05:30
639d512369 Support modifiers' render and viewport properties 2020-07-06 14:07:20 +05:30
e072382976 Add null check for poly_smooth_groups array. 2020-07-04 14:44:05 +05:30
e061df6497 Merge branch 'master' into soc-2020-io-performance 2020-07-04 14:21:32 +05:30
fe3a359fb1 Support multiple smooth groups and bitflags
For uniformity, removed `const MPoly &mpoly` from `write_vertex_groups`
and `write_poly_material`, and replaced it with poly_index. And use
`get_ith_poly` to get the polygon needed.

Removed `is_shaded_smooth`. It was wrong to compare an object level
flag with a polygon level flag. Replaced its usage with
`tot_smooth_groups()`.
2020-07-04 14:06:27 +05:30
c2eb16f662 Cleanup: style guide, reorder classes, comments
https://wiki.blender.org/wiki/Style_Guide

Changes here:
Move non trivial and non-inlined implementations to cc files instead
of header files.

Reorder classes according to style guide.

Remove documentation comments from header files and keep it near
implementation

Break up some functions into smaller ones: `insert_frame_in_path` etc.

Use reference as much as possible, instead of pointers.

Move `_` prefix to suffix of private class members.

Clean up warnings of implicit float conversions, uint conversions.

Use `ATTR_FALLTHROUGH` in switch cases. Move break inside the case
braces.

Remove `blender::io::obj::OBJ_export` function. It was a mistake.

Use `const` in getters.
2020-07-03 20:51:18 +05:30
68807cf466 Fix T78533: Skip image texture if image filepath is empty 2020-07-02 19:48:50 +05:30
d85c2620b8 Export smooth groups for smooth shaded meshes. 2020-07-02 19:28:54 +05:30
91072928cd Fix GPL license copyright year 2020-07-02 17:18:16 +05:30
42a8ea1af3 Remove object filtering code from export_frame.
Also return early if the file cannot be opened before looping over all
the objects in a frame.
2020-07-02 13:45:44 +05:30
fd4e5563fd Use BKE_mesh_calc_poly_normal
Replace custom normal calculation function with an existing function.
2020-07-02 13:43:44 +05:30
5a1ecb1702 Add null check for dvert 2020-07-02 00:32:12 +05:30
df4e43d9fd Rename get_object_deform_vert; add comments
`get_object_deform_vert` → `get_poly_deform_group_name`

Edit comments, and the UI tooltip of the export option.
2020-07-01 23:58:13 +05:30
5ee4aa5744 Merge branch 'master' into soc-2020-io-performance 2020-07-01 21:33:46 +05:30
242df25b28 Fix same group name being duplicated.
Prefix `r_` to values being modified in other functions.

Reorder arguments in vertex group and material functions for
consistency.
2020-07-01 20:18:36 +05:30
3fb230d6fe Resize deform group only upto total groups present. 2020-07-01 14:23:22 +05:30
012175f843 Support vertex group to which a face element belongs.
Differential Revision: https://developer.blender.org/D8170
2020-07-01 14:04:08 +05:30
71c6d384c1 Replace nodes_with_idname with nodes_by_type
The former was removed in
{84901f2edaf1}
2020-06-30 22:07:51 +05:30
905f470598 Merge branch 'master' into soc-2020-io-performance 2020-06-30 21:48:06 +05:30
2585882973 Cleanup: simplified conditionals, remove warnings on linux
Use unsigned int to avoid any range issues.
Remove trailing whitespaces in face elements.
Remove `== false` from conditionals.
2020-06-30 16:47:05 +05:30
9279377f4b Use unique_ptr to avoid early destruction of new meshes. 2020-06-30 16:03:15 +05:30
054f3981b1 Cleanup: AT was redundant in the destructor debug 2020-06-30 12:08:11 +05:30
fdac45d68e Remove redundant call to OBJMesh destructor. 2020-06-30 11:58:42 +05:30
e3e5ae5da8 Fix build issues: namespace BKE > bke
{b51d6e8012e5}
2020-06-30 02:36:45 +05:30
6343c98a8e Merge branch 'master' into soc-2020-io-performance 2020-06-30 02:24:54 +05:30
c2e43ca99a Remove axis transform defines and use existing enums 2020-06-30 02:13:30 +05:30
ddbc43afe0 Clean up: comments, minor refactor.
Use `const char *set_name` instead of `void get_name` which was meant
to copy the buffer originally but then was neglected.

Don't expose `_export_mesh_eval` just for ensuring normals, moved it to
another function.

Use conventional destructor function names.

Upper case enums.
2020-06-30 02:04:03 +05:30
35dc587fd6 Check if there is any material to write. 2020-06-29 20:57:17 +05:30
469398155d Null check: no image is present in the texture node 2020-06-29 20:41:50 +05:30
02fa1f0bba Fix crash when exporting uv vertices with no UV map. 2020-06-29 20:33:01 +05:30
faa11ec04a Support multiple materials in the same object.
Design has slightly changed:
The line with `usemtl` has moved just before a face element is
being written. So whenever a face with a new material is encountered,
it can be labelled.

Appending to the MTL file is still at the same place, writing once every
object.
2020-06-29 19:19:58 +05:30
bab0fce914 Support material groups: writing material name with object name 2020-06-29 14:04:51 +05:30
9086989744 Support for object groups: name of object and its mesh 2020-06-29 13:55:34 +05:30
7685d9e450 Fix socket vector being read as socket RGBA
Wrong typecasting as bNodeSocketRGBA instead of bNodeSocketVector
causes `SOCK_VECTOR`'s `type` being read as `value[0]`.

So instead of having three different functions for float, vector & color, merging
into one and specifying Datatype seems better.
2020-06-29 00:59:21 +05:30
9c6e3e103a Use findNodeSocket since it accepts const bNode*
{rB340130719f4d}
2020-06-28 15:53:37 +05:30
6529b3a09e Merge branch 'master' into soc-2020-io-performance 2020-06-28 15:33:17 +05:30
2e12333c19 Add object name in MTL related errors. 2020-06-28 14:45:12 +05:30
cfa167d57f Use Span<T> instead of const Vector<T> &
Reasons to prefer `Span`:
> - It is shorter.
> - It is const by default (whereas you could easily forget the const  in `const Vector<T> &`).
And I did forget the `const` here!
https://developer.blender.org/D7987#inline-64852
2020-06-28 14:14:19 +05:30
b22f8aec2b Typo: write OBJ & MTL comments properly with # 2020-06-28 13:49:28 +05:30
d724bf3797 Support exporting selected objects only, not the whole scene. 2020-06-28 13:47:04 +05:30
c39128ca97 UI: show warning when overwriting an export file 2020-06-28 02:46:20 +05:30
360ea0a715 Replace char * with string in texture map types.
Review update rB827869a45bccbf1e016580b#271046 (by Jacques Lucke)

As mentioned in his comments, `Map` will do a pointer comparison for
`char *` keys. So string would be better. Even though  the `lookup` has
been removed, `std::string` is still okay.

Also instead of iterating over `Map.keys()`, and then looking up value,
now it's iterating over a key-value pair: `Map<T, T>::item` of strings.

Minor comment changes.
2020-06-28 02:14:41 +05:30
eadc23273e Remove redundant blender:: from Vector, Map etc.
In rB96d6571073d3, `namespace blender` was added.
2020-06-28 01:10:37 +05:30
11e63662be Add texture transform options, Blender version comment.
Two texture transform options, out of several, are supported now:
- Translation (origin offset) (syntax: "-o u v w")
- Scale (syntax: "-s u v w")

For Normal Map textures, Strength (bump multiplier) (syntax: "-bm s")
is also added.

I had to temporarily replace `nodeFindSocket` with its own
implementation to be able to build, since it doesn't accept
`const bNode*` which we're using everywhere.

I proposed a simple fix: D8142 for that. If and when it gets committed,
I'll remove the duplicate code.

Blender version string is also added in the MTL file.

Test file: {F8647881}
2020-06-27 21:53:42 +05:30
96d6571073 Move code to namespace blender; clang-format 2020-06-27 18:49:46 +05:30
eb56b73895 Merge branch 'master' into soc-2020-io-performance 2020-06-27 18:42:38 +05:30
827869a45b Add support for exporting Material Library file
**Why not reuse the `write_mtl` in `export_obj.py` ?**
Because there are no benefits except saving some time now, only to later
waste it in debugging or extending it.
Having it in C++ provides easy extensibility when we need to
add more nodes' support without having to modify node_utils.py, which
itself is a hardcoded wrapper for BSDF and normal map shader nodes.

Quick Summary:
If export_params->export_materials is true, we create an empty MTL file
with the same name (not extension, of course) in the same directory.
`frame_writer` writes its name (with extension) to the OBJ file, to
reference it, as per format specs.

MTLWriter class is added in the new files. It uses NodeTreeRef committed
recently in rBe1cc9aa7f281 for a faster way to find two linked sockets.

`frame_writer` instantiates an MTLWriter for an object which then
*appends* that object's material to the previously created MTL file.
2020-06-27 18:36:36 +05:30
6c98925d5a Refactor: create separate files for mesh & nurbs.
`*exporter_nurbs.{cc/hh}` have been added for OBJNurbs.
`*exporter_mesh.{cc/hh}` have been added for OBJMesh.

Header file cleanup in other files.
2020-06-25 21:33:56 +05:30
42f24a134c Merge branch 'master' into soc-2020-io-performance 2020-06-25 17:54:49 +05:30
dd92c95ea7 Cleanup: clang-format. 2020-06-24 19:29:27 +05:30
ef0eff0b8a Fix repeated UV vertices and their indices
`append`'s usage causes `export_mesh_eval->totloop` many UV vertices
being written to the OBJ file. There are duplicates in that list.

So _tot_uv_vertices is being used to track the unique ones.
2020-06-24 18:13:39 +05:30
31d480174a Add support for exporting nurbs curves & surfaces
Added functionality in OBJNurbs & OBJWriter to export nurbs curve, not
as vertices and edges, but rather control points.

Nurbs surfaces are always converted to mesh and then exported as a
regular mesh with vertices, normals, UV (if it has any), and edges
(if it has no polygons) etc.
2020-06-24 15:38:42 +05:30
12fbd19ee7 Add support for curves to be exported as nurbs 2020-06-24 03:00:45 +05:30
0f383a3d7c Review update: move more things to private access
Review as per D8089:

Add constructors and destructors for both OBJWriter and OBJMesh classes

Move more variables to private and access the required via getters.

Remove unnecessary variables.

Rename ob_mesh to export_mesh_data.
2020-06-24 00:28:26 +05:30
117c990540 Optimisation: reserve memory early; use UNLIKELY
Since we know the minimum number of UV vertices that a mesh can have,
its memory is reserved in advance. `append` later in the loop simply
checks whether there's sufficient space and edits the vector.

Also, in the edge writer, the last iteration is very unlikely :).
2020-06-23 00:31:53 +05:30
d13a05a6cf Use blender::Vector and Array instead of std
The equivalent functions have also been replaced:
`back()` -> `last()`
`push_back()` -> `append()`
2020-06-23 00:31:53 +05:30
3a61338a09 Remove unused comments. 2020-06-23 00:31:53 +05:30
f2a1a66b8c Refactor: arrange the code in OOP style.
The OBJWriter class' one instance writes to one file.
All OBJWriter handles is writing to the file after calling functions
of OBJMesh which collect the required data.

OBJNurbs and OBJMaterial will be added soon too.
2020-06-23 00:31:53 +05:30
0d20c0a6ea Add support for curves to be exported as meshes.
An option in the exporter UI is added for exporting curves as NURBS.
But it doesn't do anything. All curves are exported as a mesh with
vertex coordinates and edges indexing into those coords.

The code duplication is obvious, it will be refactored to be reused
and with keeping object oriented style in mind.
2020-06-23 00:31:53 +05:30
f9289bb5ea Review update: Renaming, comments, minor refactor
Review update for comments in D7959. No new feature has been added.
Changes here:

Use PIL_time.h instead of chrono. Use BLI_path_util.h instead of stdio.

Remove redundant `_to_export` suffix from some variables.

Clarify that `object_to_export` is `ob_mesh` to distinguish it from
curves objects later on.

Edited comments.

Change in face normal calculation in one loop instead of three for the
three axes components.

Add const where required.

Not hardcode Blender version but use `BKE_blender_version_string()`.

Add filenames to error messages in file opening.
2020-06-23 00:31:53 +05:30
c4c1d7b0b9 Renaming: Specify mesh objects instead of object
No functional change is there.

This change is required to distinguish mesh objects from curve objects.
Both of them have different requirements for the structs they'd use to
store their processed data.
So instead of adding if-else everywhere, curves and meshes can be
separated into different files.
2020-06-23 00:31:53 +05:30
f3342b4bfc Merge branch 'master' into soc-2020-io-performance 2020-06-23 00:28:13 +05:30
11357e18f7 Merge branch 'master' into soc-2020-io-performance 2020-06-22 13:45:14 +05:30
b8e80fbce1 Merge branch 'master' into soc-2020-io-performance 2020-06-18 13:49:42 +05:30
c3f8e9550c Export triangulated mesh, not modifying the scene
Add UI checkbox for triangulating before writing the OBJ file.
The scene is not modified at all. Actual modifier should be used if one
needs the mesh to be modified too.

The settings for triangulation are the same as default ones of the
modifier:
ngon-method: "Beauty", quad-method: "Shortest Diagonal", min vertices:4
2020-06-18 13:45:16 +05:30
41f47cd8d6 Make UV coords and face normals optional to write
Adds options in the UI for writing normals and UV coordinates to
the OBJ file.
{F8627897}

As expected, calculating both of them is also skipped conditionally.
However, the memory for normals is still allocated; just not used.
2020-06-17 18:51:29 +05:30
d47318fb45 Add scaling factor in geometry transform. 2020-06-17 15:37:28 +05:30
4ad57b2d7f Merge branch 'master' into soc-2020-io-performance 2020-06-17 15:02:06 +05:30
1d75ece6ad Add forward and up axes transform in preferences. 2020-06-17 14:58:21 +05:30
4a59ba4bc2 Fix crash: exporting object from Edit mode. 2020-06-15 23:26:03 +05:30
4546101237 Make all frames exportable; UI & filename changes
All frames are now exportable including negative ones, instead of
only 0-1000.

Added checkbox for Animation for better UI {F8622012}

File name doesn't contain frame name if no animation is being exported.

Noticed that `source/blender/io/wavefront_obj/IO_wavefront_obj.h` is
missing in the IDE's edited files list. So edited that in.
2020-06-15 22:52:36 +05:30
a79618a719 Merge branch 'master' into soc-2020-io-performance 2020-06-15 14:49:49 +05:30
d21b3ff680 Cleanup: unused headers, minor edits in comments
Since fstream was removed, these headers are useless here.
2020-06-15 14:13:11 +05:30
6d088bdde4 Export multiple frames to separate files.
Frame 20 with mentioned filename `name` is exported to `name020.obj`
It should be modified to exclude cases when a single frame is being
exported to avoid overwriting what the user intended to name it.

Also a filter is added to include only OB_MESH type objects for export.

The limits on frames can be changed, the current limits are like this:
Let's say unknowingly, the user selects the maximum 1000 frames & every
file is 1 MB in size. That will quickly fill up 1 GB on the disk.
More frames add to this risk.
2020-06-15 13:34:44 +05:30
ad5b8bd899 Cleanup: function & variable renaming; comments. 2020-06-13 20:22:46 +05:30
c161c69179 Fix indices with offset for multiple objects.
In case of multiple objects, the vertex, normal, and UV vertex indices
keep adding up for every upcoming object.
2020-06-13 17:59:34 +05:30
40736795ec Export mutiple objects; remove unused fstream.
Adds support for multiple objects to be exported from a frame.
Also checks for them to be OB_MESH types. More object types need to be
supported yet.

Renamed data_to_export to more fitting, object_to_export of which we
use vector to contain all exportable objects.

Remove unused fstream based file writer since fprintf is consistently
faster.

Also reduce function calls of fprintf since lengthy arguments were
already removed.
2020-06-13 16:22:00 +05:30
387d96294a Merge branch 'master' into soc-2020-io-performance 2020-06-12 19:41:54 +05:30
3cbd4516c6 Change export time resolution to milliseconds 2020-06-11 22:56:30 +05:30
c2fbfb421b Fix out of bounds assert and crash in UV vector. 2020-06-11 22:53:33 +05:30
f01a7d508d Export UV vertex coordinates & their indices.
This commit adds support for UV vertex coordinates in the form:
`vt u v` for all vertices in the texture map.

Also, their indices are exported as per file format specification [1,2]
`f */vt1/* */vt2/*` for all polygons.

The text written by this and the python exporter has different order
of coordinates and thus their indices too. So direct diff of the two
files will not work.

Minor comments about 0-based/ 1-based indices are also added.

[1]: https://en.wikipedia.org/wiki/Wavefront_.obj_file#File_format
[2]: http://www.martinreddy.net/gfx/3d/OBJ.spec
2020-06-11 21:12:40 +05:30
37074c26df Comment about mvert normals memory allocation.
Readability: Replace integers to indicate axes with AXIS_{X,Y,Z}.

Consistency: Use uint with indices, like the vectors of the indices
themselves.
2020-06-11 00:21:26 +05:30
0ea70ab8e5 Add faster fprintf writer, remove unused headers
Changes here:

Reverted temporarily to std::vector to keep working, instead of
waiting for D7931 to get committed in master.

Adds an forced inline function for calculation of face/ polygon normals
by averaging vertex normals. I will look for an existing method.
This way, the allocated memory in `data_to_export->mvert[i].no` is
actually used.
Also, `BLI::Vector<uint> face_normal_index` is not needed anymore since
we loop over the same polygon list while writing normals, so
their indices will be the same.

Adds a writer method option, `fprintf`, which is faster than `fstream`.
With fstream, 478 MB of an ico-sphere with 8 subsurf takes 22 seconds.
with fprintf, the same takes 13 seconds.
With fstream, a 44 MB of cube with 8 subsurf take 2.3 seconds.
with fprintf, the same takes 1.4 seconds.

Adds timing info of the full export directly in console.

Removed unused and repeated headers from `wavefront_obj.cc`.

Differential Revision: https://developer.blender.org/D7959
2020-06-09 00:27:27 +05:30
86845ea907 Move iteration variable inside the loops.
To limit the scope & for better naming, moved the variables
`MVert *vertex` and `const Polygon &polygon` inside the loops.
2020-06-08 21:26:05 +05:30
7af1ec516f Formatting, edit comments, remove extra io::obj
Changes here:

Made variables `vertex` & `polygon` for better readability in writer.
`data_to_export->mvert`
`data_to_export->polygon_list`

Edited comments as per review in D7918.

Removed extraneous `io::obj::` being used in the same namespace.
2020-06-08 20:24:46 +05:30
b234b2fcf1 Merge branch 'master' into soc-2020-io-performance
Fixes memory leak in subsurf modifier, most probably fixed by Sergey
between 25th to 28th May 2020, in Opensubdiv improvements.
2020-06-06 20:39:54 +05:30
02c7cf5322 Namespace in lower case; rename dummy UI settings
Since capitalised namespaces could cause confusion with classes, they
are changed to lower case, in uniformity with the rest of the code.

Also, in some old settings in exporter file selector are renamed to
show that they do nothing. As William Reynish suggested on devtalk
feedback thread, at some point, it'd be good to have them drawn with
python panels. [1]

[1]: devtalk.blender.org/t/13528/2
2020-06-06 15:40:51 +05:30
083e110c49 Review: Better comments, shorter functions.
Changes here:

- Removed unused ex(im)port_params members.
- Made ex(im)port_params consistent.
- Moved `filepath` to OBJEx(Im)port_Params.
- Edited comments as per comments.
- Use <BLI_vector.hh> instead of <vector>.
- Move functions to namespace IO::OBJ.
2020-06-04 21:31:06 +05:30
5f951f96b6 Review: Added documentation & comments.
Review update. Changes here:

Rearrangement of struct members in OBJdata_to_export.

Better comments & documentation.

Linkage: internal `wavefront_obj.hh` & external `IO_wavefront_obj.h`.

Move functions to `namespace IO::OBJ`.
2020-06-04 21:11:48 +05:30
472c67eec2 Style Guide: Rename .h header files to .hh.
Since all headers internal to wavefront_obj are C++ headers, renamed
them to `.hh` following the style guide about extensions.

All occurrences of the header files & their include guards are also
modified.
2020-06-04 18:44:20 +05:30
7c19ab2c61 Rename files from obj to wavefront; move to intern
Changes here:

Move the files in `blender/io/obj` to `blender/io/obj/intern` following
the rest of the code structure.

Prefix `obj` in filenames with `wavefront_` to avoid confusion with
Blender object data type or compiled C file.

Include guards renaming according to new filenames.

`#include "*"` renames: for example
`#include "obj.h"` to  `#include "wavefront_obj.h`

Rename `bf_obj` to `bf_wavefront_obj` in CMakeLists.txt.
2020-06-04 18:31:36 +05:30
485cc4330a Preliminary geometry data exported, without any axes modification.
Single object geometry data exporter: vertex, vertex normal, faces.
Completed the todos in rB3c947bd5a6a2.

Todo:
Add object name.
Export multiple objects in the same file.
Verify it on other complex shapes.
Texture coordinates.
2020-06-02 13:19:16 +05:30
3c947bd5a6 Working UI for importer too. Got the mesh.
- Added all required dummy files.
- Finished rudimentary UI for Importer too.
- Among the several ways to get the current/ active object, picked
the most common way to get to the vertices. Might have to change
it, when _export all objects_,  etc settings appear.

TODO:
- Will finalise a singular data structure.
- Will discuss about `#include`s in `.h` vs `.cpp`.
- Filepath may be moved inside `OBJExportParams`.
- Clear up `io_obj.c`'s includes.
2020-05-27 01:32:58 +05:30
7294f0ce3d Revert the changes for ccache and lld.
These were meant to be local changes & not pushed to the GSoC branch.
After discussion with the mentors, I'm adding this reverting commit.
Moreover, LLD for mach-o is not production ready & under development.
See [1] [2].

Now building the branch will not need any modifications.

[1]: https://reviews.llvm.org/D75382
[2]: http://clang-developers.42468.n3.nabble.com/Building-clang-on-OSX-td4064374.html#a4064378
2020-05-25 17:07:29 +05:30
4864f7e281 LLD & ccache under if blocks. Use AND 0 to avoid changes. 2020-05-21 21:48:01 +05:30
3e3cee15bf Placeholder exporter UI and settings working. 2020-05-21 20:24:09 +05:30
475f15210d Placeholder files, WIP 2020-05-21 20:24:09 +05:30
e2d9b9fd6a Placeholder files, WIP 2020-05-21 20:24:09 +05:30
3cfb3360ca Linker changes. 2020-05-21 20:23:43 +05:30
781b74589a enabled ccahe; modified compilers it seems 2020-05-21 20:23:43 +05:30
42 changed files with 7256 additions and 0 deletions

View File

@@ -463,6 +463,7 @@ class TOPBAR_MT_file_import(Menu):
bl_owner_use_filter = False
def draw(self, _context):
self.layout.operator("wm.obj_import", text="Wavefront OBJ (.obj) - New")
if bpy.app.build_options.collada:
self.layout.operator("wm.collada_import",
text="Collada (Default) (.dae)")
@@ -481,6 +482,7 @@ class TOPBAR_MT_file_export(Menu):
bl_owner_use_filter = False
def draw(self, _context):
self.layout.operator("wm.obj_export", text="Wavefront OBJ (.obj) - New")
if bpy.app.build_options.collada:
self.layout.operator("wm.collada_export",
text="Collada (Default) (.dae)")

View File

@@ -26,6 +26,7 @@ set(INC
../blentranslation
../depsgraph
../draw
../editors/include
../imbuf
../makesdna
../makesrna

View File

@@ -26,9 +26,11 @@
#include "BKE_idtype.h"
#include "BKE_image.h"
#include "BKE_main.h"
#include "BKE_mball_tessellate.h"
#include "BKE_modifier.h"
#include "BKE_node.h"
#include "BKE_scene.h"
#include "BKE_vfont.h"
#include "BLI_path_util.h"
#include "BLI_threads.h"
@@ -43,6 +45,8 @@
#include "IMB_imbuf.h"
#include "ED_datafiles.h"
#include "RNA_define.h"
#include "WM_api.h"
@@ -70,6 +74,7 @@ void BlendfileLoadingBaseTest::SetUpTestCase()
DEG_register_node_types();
RNA_init();
BKE_node_system_init();
BKE_vfont_builtin_register(datatoc_bfont_pfb, datatoc_bfont_pfb_size);
G.background = true;
G.factory_startup = true;
@@ -107,6 +112,7 @@ void BlendfileLoadingBaseTest::TearDownTestCase()
void BlendfileLoadingBaseTest::TearDown()
{
BKE_mball_cubeTable_free();
depsgraph_free();
blendfile_free();

View File

@@ -25,6 +25,20 @@ set(INC
../../io/alembic
../../io/collada
../../io/gpencil
../../io/wavefront_obj
../../io/usd
../../makesdna
../../makesrna
../../windowmanager
../../../../intern/guardedalloc
)
set(INC_SYS
)
set(SRC
io_alembic.c
../../io/usd
../../makesdna
../../makesrna
@@ -43,6 +57,7 @@ set(SRC
io_gpencil_export.c
io_gpencil_import.c
io_gpencil_utils.c
io_obj.c
io_ops.c
io_usd.c
@@ -57,6 +72,7 @@ set(SRC
set(LIB
bf_blenkernel
bf_blenlib
bf_wavefront_obj
)
if(WITH_OPENCOLLADA)

View File

@@ -0,0 +1,456 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup editor/io
*/
#include "DNA_space_types.h"
#include "BKE_context.h"
#include "BKE_main.h"
#include "BKE_report.h"
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "BLI_utildefines.h"
#include "BLT_translation.h"
#include "MEM_guardedalloc.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "UI_interface.h"
#include "UI_resources.h"
#include "WM_api.h"
#include "WM_types.h"
#include "DEG_depsgraph.h"
#include "IO_wavefront_obj.h"
#include "io_obj.h"
const EnumPropertyItem io_obj_transform_axis_forward[] = {
{OBJ_AXIS_X_FORWARD, "X_FORWARD", 0, "X", "Positive X axis"},
{OBJ_AXIS_Y_FORWARD, "Y_FORWARD", 0, "Y", "Positive Y axis"},
{OBJ_AXIS_Z_FORWARD, "Z_FORWARD", 0, "Z", "Positive Z axis"},
{OBJ_AXIS_NEGATIVE_X_FORWARD, "NEGATIVE_X_FORWARD", 0, "-X", "Negative X axis"},
{OBJ_AXIS_NEGATIVE_Y_FORWARD, "NEGATIVE_Y_FORWARD", 0, "-Y", "Negative Y axis"},
{OBJ_AXIS_NEGATIVE_Z_FORWARD, "NEGATIVE_Z_FORWARD", 0, "-Z (Default)", "Negative Z axis"},
{0, NULL, 0, NULL, NULL}};
const EnumPropertyItem io_obj_transform_axis_up[] = {
{OBJ_AXIS_X_UP, "X_UP", 0, "X", "Positive X axis"},
{OBJ_AXIS_Y_UP, "Y_UP", 0, "Y (Default)", "Positive Y axis"},
{OBJ_AXIS_Z_UP, "Z_UP", 0, "Z", "Positive Z axis"},
{OBJ_AXIS_NEGATIVE_X_UP, "NEGATIVE_X_UP", 0, "-X", "Negative X axis"},
{OBJ_AXIS_NEGATIVE_Y_UP, "NEGATIVE_Y_UP", 0, "-Y", "Negative Y axis"},
{OBJ_AXIS_NEGATIVE_Z_UP, "NEGATIVE_Z_UP", 0, "-Z", "Negative Z axis"},
{0, NULL, 0, NULL, NULL}};
const EnumPropertyItem io_obj_export_evaluation_mode[] = {
{DAG_EVAL_RENDER, "DAG_EVAL_RENDER", 0, "Render", "Export objects as they appear in render"},
{DAG_EVAL_VIEWPORT,
"DAG_EVAL_VIEWPORT",
0,
"Viewport (Default)",
"Export objects as they appear in the viewport"},
{0, NULL, 0, NULL, NULL}};
static int wm_obj_export_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
{
if (!RNA_struct_property_is_set(op->ptr, "filepath")) {
Main *bmain = CTX_data_main(C);
char filepath[FILE_MAX];
if (BKE_main_blendfile_path(bmain)[0] == '\0') {
BLI_strncpy(filepath, "untitled", sizeof(filepath));
}
else {
BLI_strncpy(filepath, BKE_main_blendfile_path(bmain), sizeof(filepath));
}
BLI_path_extension_replace(filepath, sizeof(filepath), ".obj");
RNA_string_set(op->ptr, "filepath", filepath);
}
WM_event_add_fileselect(C, op);
return OPERATOR_RUNNING_MODAL;
}
static int wm_obj_export_exec(bContext *C, wmOperator *op)
{
if (!RNA_struct_property_is_set(op->ptr, "filepath")) {
BKE_report(op->reports, RPT_ERROR, "No filename given");
return OPERATOR_CANCELLED;
}
struct OBJExportParams export_params;
RNA_string_get(op->ptr, "filepath", export_params.filepath);
export_params.blen_filepath = CTX_data_main(C)->name;
export_params.export_animation = RNA_boolean_get(op->ptr, "export_animation");
export_params.start_frame = RNA_int_get(op->ptr, "start_frame");
export_params.end_frame = RNA_int_get(op->ptr, "end_frame");
export_params.forward_axis = RNA_enum_get(op->ptr, "forward_axis");
export_params.up_axis = RNA_enum_get(op->ptr, "up_axis");
export_params.scaling_factor = RNA_float_get(op->ptr, "scaling_factor");
export_params.export_eval_mode = RNA_enum_get(op->ptr, "export_eval_mode");
export_params.export_selected_objects = RNA_boolean_get(op->ptr, "export_selected_objects");
export_params.export_uv = RNA_boolean_get(op->ptr, "export_uv");
export_params.export_normals = RNA_boolean_get(op->ptr, "export_normals");
export_params.export_materials = RNA_boolean_get(op->ptr, "export_materials");
export_params.export_triangulated_mesh = RNA_boolean_get(op->ptr, "export_triangulated_mesh");
export_params.export_curves_as_nurbs = RNA_boolean_get(op->ptr, "export_curves_as_nurbs");
export_params.export_object_groups = RNA_boolean_get(op->ptr, "export_object_groups");
export_params.export_material_groups = RNA_boolean_get(op->ptr, "export_material_groups");
export_params.export_vertex_groups = RNA_boolean_get(op->ptr, "export_vertex_groups");
export_params.export_smooth_groups = RNA_boolean_get(op->ptr, "export_smooth_groups");
export_params.smooth_groups_bitflags = RNA_boolean_get(op->ptr, "smooth_group_bitflags");
OBJ_export(C, &export_params);
return OPERATOR_FINISHED;
}
static void ui_obj_export_settings(uiLayout *layout, PointerRNA *imfptr)
{
const bool export_animation = RNA_boolean_get(imfptr, "export_animation");
const bool export_smooth_groups = RNA_boolean_get(imfptr, "export_smooth_groups");
uiLayoutSetPropSep(layout, true);
uiLayoutSetPropDecorate(layout, false);
/* Animation options. */
uiLayout *box = uiLayoutBox(layout);
uiItemL(box, IFACE_("Animation"), ICON_ANIM);
uiLayout *col = uiLayoutColumn(box, false);
uiLayout *sub = uiLayoutColumn(col, false);
uiItemR(sub, imfptr, "export_animation", 0, NULL, ICON_NONE);
sub = uiLayoutColumn(sub, true);
uiItemR(sub, imfptr, "start_frame", 0, IFACE_("Frame Start"), ICON_NONE);
uiItemR(sub, imfptr, "end_frame", 0, IFACE_("End"), ICON_NONE);
uiLayoutSetEnabled(sub, export_animation);
/* Object Transform options. */
box = uiLayoutBox(layout);
uiItemL(box, IFACE_("Object Properties"), ICON_OBJECT_DATA);
col = uiLayoutColumn(box, false);
sub = uiLayoutColumn(col, false);
uiItemR(sub, imfptr, "forward_axis", 0, IFACE_("Axis Forward"), ICON_NONE);
uiItemR(sub, imfptr, "up_axis", 0, IFACE_("Up"), ICON_NONE);
sub = uiLayoutColumn(col, false);
uiItemR(sub, imfptr, "scaling_factor", 0, NULL, ICON_NONE);
sub = uiLayoutColumnWithHeading(col, false, IFACE_("Objects"));
uiItemR(sub, imfptr, "export_selected_objects", 0, IFACE_("Selected Only"), ICON_NONE);
uiItemR(sub, imfptr, "export_eval_mode", 0, IFACE_("Properties"), ICON_NONE);
/* Options for what to write. */
box = uiLayoutBox(layout);
uiItemL(box, IFACE_("Geometry Export"), ICON_EXPORT);
col = uiLayoutColumn(box, false);
sub = uiLayoutColumnWithHeading(col, false, IFACE_("Export"));
uiItemR(sub, imfptr, "export_uv", 0, IFACE_("UV Coordinates"), ICON_NONE);
uiItemR(sub, imfptr, "export_normals", 0, IFACE_("Normals"), ICON_NONE);
uiItemR(sub, imfptr, "export_materials", 0, IFACE_("Materials"), ICON_NONE);
uiItemR(sub, imfptr, "export_triangulated_mesh", 0, IFACE_("Triangulated Mesh"), ICON_NONE);
uiItemR(sub, imfptr, "export_curves_as_nurbs", 0, IFACE_("Curves as NURBS"), ICON_NONE);
box = uiLayoutBox(layout);
uiItemL(box, IFACE_("Grouping"), ICON_GROUP);
col = uiLayoutColumn(box, false);
sub = uiLayoutColumnWithHeading(col, false, IFACE_("Export"));
uiItemR(sub, imfptr, "export_object_groups", 0, IFACE_("Object Groups"), ICON_NONE);
uiItemR(sub, imfptr, "export_material_groups", 0, IFACE_("Material Groups"), ICON_NONE);
uiItemR(sub, imfptr, "export_vertex_groups", 0, IFACE_("Vertex Groups"), ICON_NONE);
uiItemR(sub, imfptr, "export_smooth_groups", 0, IFACE_("Smooth Groups"), ICON_NONE);
sub = uiLayoutColumn(sub, false);
uiLayoutSetEnabled(sub, export_smooth_groups);
uiItemR(sub, imfptr, "smooth_group_bitflags", 0, IFACE_("Smooth Group Bitflags"), ICON_NONE);
}
static void wm_obj_export_draw(bContext *UNUSED(C), wmOperator *op)
{
PointerRNA ptr;
RNA_pointer_create(NULL, op->type->srna, op->properties, &ptr);
ui_obj_export_settings(op->layout, &ptr);
}
/**
* Return true if any property in the UI is changed.
*/
static bool wm_obj_export_check(bContext *C, wmOperator *op)
{
char filepath[FILE_MAX];
Scene *scene = CTX_data_scene(C);
bool changed = false;
RNA_string_get(op->ptr, "filepath", filepath);
if (!BLI_path_extension_check(filepath, ".obj")) {
BLI_path_extension_ensure(filepath, FILE_MAX, ".obj");
RNA_string_set(op->ptr, "filepath", filepath);
changed = true;
}
{
int start = RNA_int_get(op->ptr, "start_frame");
int end = RNA_int_get(op->ptr, "end_frame");
/* Set the defaults. */
if (start == INT_MIN) {
start = SFRA;
changed = true;
}
if (end == INT_MAX) {
end = EFRA;
changed = true;
}
/* Fix user errors. */
if (end < start) {
end = start;
changed = true;
}
RNA_int_set(op->ptr, "start_frame", start);
RNA_int_set(op->ptr, "end_frame", end);
}
/* Both forward and up axes cannot be the same (or same except opposite sign). */
if (RNA_enum_get(op->ptr, "forward_axis") % TOTAL_AXES ==
(RNA_enum_get(op->ptr, "up_axis") % TOTAL_AXES)) {
/* TODO (ankitm) Show a warning here. */
RNA_enum_set(op->ptr, "up_axis", RNA_enum_get(op->ptr, "up_axis") % TOTAL_AXES + 1);
changed = true;
}
return changed;
}
void WM_OT_obj_export(struct wmOperatorType *ot)
{
ot->name = "Export Wavefront OBJ";
ot->description = "Save the scene to a Wavefront OBJ file";
ot->idname = "WM_OT_obj_export";
ot->invoke = wm_obj_export_invoke;
ot->exec = wm_obj_export_exec;
ot->poll = WM_operator_winactive;
ot->ui = wm_obj_export_draw;
ot->check = wm_obj_export_check;
WM_operator_properties_filesel(ot,
FILE_TYPE_FOLDER | FILE_TYPE_OBJECT_IO,
FILE_BLENDER,
FILE_SAVE,
WM_FILESEL_FILEPATH | WM_FILESEL_SHOW_PROPS,
FILE_DEFAULTDISPLAY,
FILE_SORT_ALPHA);
/* Animation options. */
RNA_def_boolean(ot->srna,
"export_animation",
false,
"Export Animation",
"Export multiple frames instead of the current frame only");
RNA_def_int(ot->srna,
"start_frame",
INT_MIN, /* wm_obj_export_check uses this to set SFRA. */
INT_MIN,
INT_MAX,
"Start Frame",
"The first frame to be exported",
INT_MIN,
INT_MAX);
RNA_def_int(ot->srna,
"end_frame",
INT_MAX, /* wm_obj_export_check uses this to set EFRA. */
INT_MIN,
INT_MAX,
"End Frame",
"The last frame to be exported",
INT_MIN,
INT_MAX);
/* Object transform options. */
RNA_def_enum(ot->srna,
"forward_axis",
io_obj_transform_axis_forward,
OBJ_AXIS_NEGATIVE_Z_FORWARD,
"Forward Axis",
"");
RNA_def_enum(ot->srna, "up_axis", io_obj_transform_axis_up, OBJ_AXIS_Y_UP, "Up Axis", "");
RNA_def_float(ot->srna,
"scaling_factor",
1.0f,
0.001f,
10000.0f,
"Scale",
"Upscale the object by this factor",
0.01,
1000.0f);
/* File Writer options. */
RNA_def_enum(ot->srna,
"export_eval_mode",
io_obj_export_evaluation_mode,
DAG_EVAL_VIEWPORT,
"Object Properties",
"Determines properties like object visibility, modifiers etc., where they differ "
"for Render and Viewport");
RNA_def_boolean(ot->srna,
"export_selected_objects",
false,
"Export Selected Objects",
"Export only selected objects instead of all supported objects");
RNA_def_boolean(ot->srna, "export_uv", true, "Export UVs", "");
RNA_def_boolean(ot->srna,
"export_normals",
true,
"Export Normals",
"Export per-face normals if the face is flat-shaded, per-face-per-loop "
"normals if smooth-shaded");
RNA_def_boolean(ot->srna,
"export_materials",
true,
"Export Materials",
"Export MTL library. There must be a Principled-BSDF node for image textures to "
"be exported to the MTL file");
RNA_def_boolean(ot->srna,
"export_triangulated_mesh",
false,
"Export Triangulated Mesh",
"All ngons with four or more vertices will be triangulated. Meshes in "
"the scene will not be affected. Behaves like Triangulate Modifier with "
"ngon-method: \"Beauty\", quad-method: \"Shortest Diagonal\", min vertices: 4");
RNA_def_boolean(ot->srna,
"export_curves_as_nurbs",
false,
"Export Curves as NURBS",
"Export curves in parametric form instead of exporting as mesh");
RNA_def_boolean(ot->srna,
"export_object_groups",
false,
"Export Object Groups",
"Append mesh name to object name, separated by a '_'");
RNA_def_boolean(ot->srna,
"export_material_groups",
false,
"Export Material Groups",
"Append mesh name and material name to object name, separated by a '_'");
RNA_def_boolean(
ot->srna,
"export_vertex_groups",
false,
"Export Vertex Groups",
"Export the name of the vertex group of a face. It is approximated "
"by choosing the vertex group with the most members among the vertices of a face");
RNA_def_boolean(
ot->srna,
"export_smooth_groups",
false,
"Export Smooth Groups",
"Every smooth-shaded face is assigned group \"1\" and every flat-shaded face \"off\"");
RNA_def_boolean(
ot->srna, "smooth_group_bitflags", false, "Generate Bitflags for Smooth Groups", "");
}
static int wm_obj_import_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
{
WM_event_add_fileselect(C, op);
return OPERATOR_RUNNING_MODAL;
}
static int wm_obj_import_exec(bContext *C, wmOperator *op)
{
if (!RNA_struct_property_is_set(op->ptr, "filepath")) {
BKE_report(op->reports, RPT_ERROR, "No filename given");
return OPERATOR_CANCELLED;
}
struct OBJImportParams import_params;
RNA_string_get(op->ptr, "filepath", import_params.filepath);
import_params.clamp_size = RNA_float_get(op->ptr, "clamp_size");
import_params.forward_axis = RNA_enum_get(op->ptr, "forward_axis");
import_params.up_axis = RNA_enum_get(op->ptr, "up_axis");
OBJ_import(C, &import_params);
return OPERATOR_FINISHED;
}
static void ui_obj_import_settings(uiLayout *layout, PointerRNA *imfptr)
{
uiLayoutSetPropSep(layout, true);
uiLayoutSetPropDecorate(layout, false);
uiLayout *box = uiLayoutBox(layout);
uiItemL(box, IFACE_("Transform"), ICON_OBJECT_DATA);
uiLayout *col = uiLayoutColumn(box, false);
uiLayout *sub = uiLayoutColumn(col, false);
uiItemR(sub, imfptr, "clamp_size", 0, NULL, ICON_NONE);
sub = uiLayoutColumn(col, false);
uiItemR(sub, imfptr, "forward_axis", 0, IFACE_("Axis Forward"), ICON_NONE);
uiItemR(sub, imfptr, "up_axis", 0, IFACE_("Up"), ICON_NONE);
}
static void wm_obj_import_draw(bContext *C, wmOperator *op)
{
PointerRNA ptr;
wmWindowManager *wm = CTX_wm_manager(C);
RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr);
ui_obj_import_settings(op->layout, &ptr);
}
void WM_OT_obj_import(struct wmOperatorType *ot)
{
ot->name = "Import Wavefront OBJ";
ot->description = "Load a Wavefront OBJ scene";
ot->idname = "WM_OT_obj_import";
ot->invoke = wm_obj_import_invoke;
ot->exec = wm_obj_import_exec;
ot->poll = WM_operator_winactive;
ot->ui = wm_obj_import_draw;
WM_operator_properties_filesel(ot,
FILE_TYPE_FOLDER | FILE_TYPE_OBJECT_IO,
FILE_BLENDER,
FILE_OPENFILE,
WM_FILESEL_FILEPATH | WM_FILESEL_SHOW_PROPS,
FILE_DEFAULTDISPLAY,
FILE_SORT_ALPHA);
RNA_def_float(
ot->srna,
"clamp_size",
0.0f,
0.0f,
1000.0f,
"Clamp Bounding Box",
"Resize the objects to keep bounding box under this value. Value 0 diables clamping",
0.0f,
1000.0f);
RNA_def_enum(ot->srna,
"forward_axis",
io_obj_transform_axis_forward,
OBJ_AXIS_NEGATIVE_Z_FORWARD,
"Forward Axis",
"");
RNA_def_enum(ot->srna, "up_axis", io_obj_transform_axis_up, OBJ_AXIS_Y_UP, "Up Axis", "");
}

View File

@@ -0,0 +1,29 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup editor/io
*/
#pragma once
struct wmOperatorType;
void WM_OT_obj_export(struct wmOperatorType *ot);
void WM_OT_obj_import(struct wmOperatorType *ot);

View File

@@ -39,6 +39,7 @@
#include "io_cache.h"
#include "io_gpencil.h"
#include "io_obj.h"
void ED_operatortypes_io(void)
{
@@ -68,4 +69,6 @@ void ED_operatortypes_io(void)
WM_operatortype_append(CACHEFILE_OT_open);
WM_operatortype_append(CACHEFILE_OT_reload);
WM_operatortype_append(WM_OT_obj_import);
WM_operatortype_append(WM_OT_obj_export);
}

View File

@@ -275,6 +275,9 @@ static FileSelectParams *fileselect_ensure_updated_file_params(SpaceFile *sfile)
if ((prop = RNA_struct_find_property(op->ptr, "filter_usd"))) {
params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_USD : 0;
}
if ((prop = RNA_struct_find_property(op->ptr, "filter_obj"))) {
params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_OBJECT_IO : 0;
}
if ((prop = RNA_struct_find_property(op->ptr, "filter_volume"))) {
params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_VOLUME : 0;
}

View File

@@ -19,6 +19,7 @@
# ***** END GPL LICENSE BLOCK *****
add_subdirectory(common)
add_subdirectory(wavefront_obj)
if(WITH_ALEMBIC)
add_subdirectory(alembic)

View File

@@ -0,0 +1,104 @@
# ***** BEGIN GPL LICENSE BLOCK *****
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# The Original Code is Copyright (C) 2020, Blender Foundation
# All rights reserved.
# ***** END GPL LICENSE BLOCK *****
set(INC
.
./exporter
./importer
../../blenkernel
../../blenlib
../../bmesh
../../bmesh/intern
../../depsgraph
../../editors/include
../../makesdna
../../makesrna
../../nodes
../../windowmanager
../../../../intern/guardedalloc
)
set(INC_SYS
)
set(SRC
IO_wavefront_obj.cc
exporter/obj_exporter.cc
exporter/obj_export_file_writer.cc
exporter/obj_export_mesh.cc
exporter/obj_export_mtl.cc
exporter/obj_export_nurbs.cc
importer/importer_mesh_utils.cc
importer/obj_import_file_reader.cc
importer/obj_importer.cc
importer/obj_import_mesh.cc
importer/obj_import_mtl.cc
importer/obj_import_nurbs.cc
importer/obj_import_objects.cc
importer/parser_string_utils.cc
IO_wavefront_obj.h
exporter/obj_exporter.hh
exporter/obj_export_file_writer.hh
exporter/obj_export_io.hh
exporter/obj_export_mesh.hh
exporter/obj_export_mtl.hh
exporter/obj_export_nurbs.hh
importer/importer_mesh_utils.hh
importer/obj_import_file_reader.hh
importer/obj_importer.hh
importer/obj_import_mesh.hh
importer/obj_import_mtl.hh
importer/obj_import_nurbs.hh
importer/obj_import_objects.hh
importer/parser_string_utils.hh
)
set(LIB
bf_blenkernel
)
blender_add_lib(bf_wavefront_obj "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
if(WITH_GTESTS)
set(TEST_SRC
tests/obj_exporter_tests.cc
tests/obj_exporter_tests.hh
)
set(TEST_INC
${INC}
../../blenloader
../../../../tests/gtests
)
set(TEST_LIB
${LIB}
bf_blenloader_tests
bf_wavefront_obj
)
include(GTestTesting)
blender_add_test_lib(bf_wavefront_obj_tests "${TEST_SRC}" "${TEST_INC}" "${INC_SYS}" "${TEST_LIB}")
add_dependencies(bf_wavefront_obj_tests bf_wavefront_obj)
endif()

View File

@@ -0,0 +1,47 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup obj
*/
#include "BLI_timeit.hh"
#include "IO_wavefront_obj.h"
#include "obj_exporter.hh"
#include "obj_importer.hh"
/**
* C-interface for the exporter.
*/
void OBJ_export(bContext *C, const OBJExportParams *export_params)
{
SCOPED_TIMER("OBJ export");
blender::io::obj::exporter_main(C, *export_params);
}
/**
* Time the full import process.
*/
void OBJ_import(bContext *C, const OBJImportParams *import_params)
{
SCOPED_TIMER(__func__);
blender::io::obj::importer_main(C, *import_params);
}

View File

@@ -0,0 +1,111 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup obj
*/
#pragma once
#include "BKE_context.h"
#include "BLI_path_util.h"
#include "DEG_depsgraph.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
OBJ_AXIS_X_UP = 0,
OBJ_AXIS_Y_UP = 1,
OBJ_AXIS_Z_UP = 2,
OBJ_AXIS_NEGATIVE_X_UP = 3,
OBJ_AXIS_NEGATIVE_Y_UP = 4,
OBJ_AXIS_NEGATIVE_Z_UP = 5,
} eTransformAxisUp;
typedef enum {
OBJ_AXIS_X_FORWARD = 0,
OBJ_AXIS_Y_FORWARD = 1,
OBJ_AXIS_Z_FORWARD = 2,
OBJ_AXIS_NEGATIVE_X_FORWARD = 3,
OBJ_AXIS_NEGATIVE_Y_FORWARD = 4,
OBJ_AXIS_NEGATIVE_Z_FORWARD = 5,
} eTransformAxisForward;
const int TOTAL_AXES = 3;
struct OBJExportParams {
/** Full path to the destination .OBJ file. */
char filepath[FILE_MAX];
/** Full path to current blender file (used for comments in output). */
const char *blen_filepath;
/** Whether multiple frames should be exported. */
bool export_animation;
/** The first frame to be exported. */
int start_frame;
/** The last frame to be exported. */
int end_frame;
/* Geometry Transform options. */
eTransformAxisForward forward_axis;
eTransformAxisUp up_axis;
float scaling_factor;
/* File Write Options. */
bool export_selected_objects;
eEvaluationMode export_eval_mode;
bool export_uv;
bool export_normals;
bool export_materials;
bool export_triangulated_mesh;
bool export_curves_as_nurbs;
/* Grouping options. */
bool export_object_groups;
bool export_material_groups;
bool export_vertex_groups;
/**
* Calculate smooth groups from sharp edges.
*/
bool export_smooth_groups;
/**
* Create bitflags instead of the default "0"/"1" group IDs.
*/
bool smooth_groups_bitflags;
};
struct OBJImportParams {
/** Full path to the source OBJ file to import. */
char filepath[FILE_MAX];
/* Value 0 disables clamping. */
float clamp_size;
eTransformAxisForward forward_axis;
eTransformAxisUp up_axis;
};
void OBJ_import(bContext *C, const struct OBJImportParams *import_params);
void OBJ_export(bContext *C, const struct OBJExportParams *export_params);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,629 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup obj
*/
#include <algorithm>
#include <cstdio>
#include "BKE_blender_version.h"
#include "BLI_path_util.h"
#include "obj_export_mesh.hh"
#include "obj_export_mtl.hh"
#include "obj_export_nurbs.hh"
#include "obj_export_file_writer.hh"
namespace blender::io::obj {
/**
* "To turn off smoothing
* groups, use a value of 0 or off. Polygonal elements use group
* numbers to put elements in different smoothing groups. For
* free-form surfaces, smoothing groups are either turned on or off;
* there is no difference between values greater than 0."
* http://www.martinreddy.net/gfx/3d/OBJ.spec
*/
const int SMOOTH_GROUP_DISABLED = 0;
const int SMOOTH_GROUP_DEFAULT = 1;
const char *DEFORM_GROUP_DISABLED = "off";
/* There is no deform group default name. Use what the user set in the UI. */
/* "Once a material is assigned, it cannot be turned off; it can only be changed.
* If a material name is not specified, a white material is used."
* http://www.martinreddy.net/gfx/3d/OBJ.spec
* So an empty material name is written. */
const char *MATERIAL_GROUP_DISABLED = "";
/**
* Write one line of polygon indices as "f v1/vt1/vn1 v2/vt2/vn2 ...".
*/
void OBJWriter::write_vert_uv_normal_indices(Span<int> vert_indices,
Span<int> uv_indices,
Span<int> normal_indices) const
{
BLI_assert(vert_indices.size() == uv_indices.size() &&
vert_indices.size() == normal_indices.size());
file_handler_->write<eOBJSyntaxElement::poly_element_begin>();
for (int j = 0; j < vert_indices.size(); j++) {
file_handler_->write<eOBJSyntaxElement::vertex_uv_normal_indices>(
vert_indices[j] + index_offsets_.vertex_offset + 1,
uv_indices[j] + index_offsets_.uv_vertex_offset + 1,
normal_indices[j] + index_offsets_.normal_offset + 1);
}
file_handler_->write<eOBJSyntaxElement::poly_element_end>();
}
/**
* Write one line of polygon indices as "f v1//vn1 v2//vn2 ...".
*/
void OBJWriter::write_vert_normal_indices(Span<int> vert_indices,
Span<int> /*uv_indices*/,
Span<int> normal_indices) const
{
BLI_assert(vert_indices.size() == normal_indices.size());
file_handler_->write<eOBJSyntaxElement::poly_element_begin>();
for (int j = 0; j < vert_indices.size(); j++) {
file_handler_->write<eOBJSyntaxElement::vertex_normal_indices>(
vert_indices[j] + index_offsets_.vertex_offset + 1,
normal_indices[j] + index_offsets_.normal_offset + 1);
}
file_handler_->write<eOBJSyntaxElement::poly_element_end>();
}
/**
* Write one line of polygon indices as "f v1/vt1 v2/vt2 ...".
*/
void OBJWriter::write_vert_uv_indices(Span<int> vert_indices,
Span<int> uv_indices,
Span<int> /*normal_indices*/) const
{
BLI_assert(vert_indices.size() == uv_indices.size());
file_handler_->write<eOBJSyntaxElement::poly_element_begin>();
for (int j = 0; j < vert_indices.size(); j++) {
file_handler_->write<eOBJSyntaxElement::vertex_uv_indices>(
vert_indices[j] + index_offsets_.vertex_offset + 1,
uv_indices[j] + index_offsets_.uv_vertex_offset + 1);
}
file_handler_->write<eOBJSyntaxElement::poly_element_end>();
}
/**
* Write one line of polygon indices as "f v1 v2 ...".
*/
void OBJWriter::write_vert_indices(Span<int> vert_indices,
Span<int> /*uv_indices*/,
Span<int> /*normal_indices*/) const
{
file_handler_->write<eOBJSyntaxElement::poly_element_begin>();
for (const int vert_index : vert_indices) {
file_handler_->write<eOBJSyntaxElement::vertex_indices>(vert_index +
index_offsets_.vertex_offset + 1);
}
file_handler_->write<eOBJSyntaxElement::poly_element_end>();
}
void OBJWriter::write_header() const
{
using namespace std::string_literals;
file_handler_->write<eOBJSyntaxElement::string>("# Blender "s + BKE_blender_version_string() +
"\n");
file_handler_->write<eOBJSyntaxElement::string>("# www.blender.org\n");
}
/**
* Write file name of Material Library in .OBJ file.
*/
void OBJWriter::write_mtllib_name(const StringRefNull mtl_filepath) const
{
/* Split .MTL file path into parent directory and filename. */
char mtl_file_name[FILE_MAXFILE];
char mtl_dir_name[FILE_MAXDIR];
BLI_split_dirfile(mtl_filepath.data(), mtl_dir_name, mtl_file_name, FILE_MAXDIR, FILE_MAXFILE);
file_handler_->write<eOBJSyntaxElement::mtllib>(mtl_file_name);
}
/**
* Write an object's group with mesh and/or material name appended conditionally.
*/
void OBJWriter::write_object_group(const OBJMesh &obj_mesh_data) const
{
/* "o object_name" is not mandatory. A valid .OBJ file may contain neither
* "o name" nor "g group_name". */
BLI_assert(export_params_.export_object_groups);
if (!export_params_.export_object_groups) {
return;
}
const std::string object_name = obj_mesh_data.get_object_name();
const char *object_mesh_name = obj_mesh_data.get_object_mesh_name();
const char *object_material_name = obj_mesh_data.get_object_material_name(0);
if (export_params_.export_materials && export_params_.export_material_groups &&
object_material_name) {
file_handler_->write<eOBJSyntaxElement::object_group>(object_name + "_" + object_mesh_name +
"_" + object_material_name);
return;
}
file_handler_->write<eOBJSyntaxElement::object_group>(object_name + "_" + object_mesh_name);
}
/**
* Write object's name or group.
*/
void OBJWriter::write_object_name(const OBJMesh &obj_mesh_data) const
{
const char *object_name = obj_mesh_data.get_object_name();
if (export_params_.export_object_groups) {
write_object_group(obj_mesh_data);
return;
}
file_handler_->write<eOBJSyntaxElement::object_name>(object_name);
}
/**
* Write vertex coordinates for all vertices as "v x y z".
*/
void OBJWriter::write_vertex_coords(const OBJMesh &obj_mesh_data) const
{
const int tot_vertices = obj_mesh_data.tot_vertices();
for (int i = 0; i < tot_vertices; i++) {
float3 vertex = obj_mesh_data.calc_vertex_coords(i, export_params_.scaling_factor);
file_handler_->write<eOBJSyntaxElement::vertex_coords>(vertex[0], vertex[1], vertex[2]);
}
}
/**
* Write UV vertex coordinates for all vertices as "vt u v".
* \note UV indices are stored here, but written later.
*/
void OBJWriter::write_uv_coords(OBJMesh &r_obj_mesh_data) const
{
Vector<std::array<float, 2>> uv_coords;
/* UV indices are calculated and stored in an OBJMesh member here. */
r_obj_mesh_data.store_uv_coords_and_indices(uv_coords);
for (const std::array<float, 2> &uv_vertex : uv_coords) {
file_handler_->write<eOBJSyntaxElement::uv_vertex_coords>(uv_vertex[0], uv_vertex[1]);
}
}
/**
* Write loop normals for smooth-shaded polygons, and polygon normals otherwise, as "vn x y z".
*/
void OBJWriter::write_poly_normals(const OBJMesh &obj_mesh_data) const
{
obj_mesh_data.ensure_mesh_normals();
Vector<float3> lnormals;
const int tot_polygons = obj_mesh_data.tot_polygons();
for (int i = 0; i < tot_polygons; i++) {
if (obj_mesh_data.is_ith_poly_smooth(i)) {
obj_mesh_data.calc_loop_normals(i, lnormals);
for (const float3 &lnormal : lnormals) {
file_handler_->write<eOBJSyntaxElement::normal>(lnormal[0], lnormal[1], lnormal[2]);
}
}
else {
float3 poly_normal = obj_mesh_data.calc_poly_normal(i);
file_handler_->write<eOBJSyntaxElement::normal>(
poly_normal[0], poly_normal[1], poly_normal[2]);
}
}
}
/**
* Write smooth group if polygon at the given index is shaded smooth else "s 0"
*/
int OBJWriter::write_smooth_group(const OBJMesh &obj_mesh_data,
const int poly_index,
const int last_poly_smooth_group) const
{
int current_group = SMOOTH_GROUP_DISABLED;
if (!export_params_.export_smooth_groups && obj_mesh_data.is_ith_poly_smooth(poly_index)) {
/* Smooth group calculation is disabled, but polygon is smooth-shaded. */
current_group = SMOOTH_GROUP_DEFAULT;
}
else if (obj_mesh_data.is_ith_poly_smooth(poly_index)) {
/* Smooth group calc is enabled and polygon is smoothshaded, so find the group. */
current_group = obj_mesh_data.ith_smooth_group(poly_index);
}
if (current_group == last_poly_smooth_group) {
/* Group has already been written, even if it is "s 0". */
return current_group;
}
file_handler_->write<eOBJSyntaxElement::smooth_group>(current_group);
return current_group;
}
/**
* Write material name and material group of a polygon in the .OBJ file.
* \return #mat_nr of the polygon at the given index.
* \note It doesn't write to the material library.
*/
int16_t OBJWriter::write_poly_material(const OBJMesh &obj_mesh_data,
const int poly_index,
const int16_t last_poly_mat_nr,
std::function<const char *(int)> matname_fn) const
{
if (!export_params_.export_materials || obj_mesh_data.tot_materials() <= 0) {
return last_poly_mat_nr;
}
const int16_t current_mat_nr = obj_mesh_data.ith_poly_matnr(poly_index);
/* Whenever a polygon with a new material is encountered, write its material
* and/or group, otherwise pass. */
if (last_poly_mat_nr == current_mat_nr) {
return current_mat_nr;
}
if (current_mat_nr == NOT_FOUND) {
file_handler_->write<eOBJSyntaxElement::poly_usemtl>(MATERIAL_GROUP_DISABLED);
return current_mat_nr;
}
if (export_params_.export_object_groups) {
write_object_group(obj_mesh_data);
}
const char *mat_name = matname_fn(current_mat_nr);
if (!mat_name) {
mat_name = MATERIAL_GROUP_DISABLED;
}
file_handler_->write<eOBJSyntaxElement::poly_usemtl>(mat_name);
return current_mat_nr;
}
/**
* Write the name of the deform group of a polygon.
*/
int16_t OBJWriter::write_vertex_group(const OBJMesh &obj_mesh_data,
const int poly_index,
const int16_t last_poly_vertex_group) const
{
if (!export_params_.export_vertex_groups) {
return last_poly_vertex_group;
}
const int16_t current_group = obj_mesh_data.get_poly_deform_group_index(poly_index);
if (current_group == last_poly_vertex_group) {
/* No vertex group found in this polygon, just like in the last iteration. */
return current_group;
}
if (current_group == NOT_FOUND) {
file_handler_->write<eOBJSyntaxElement::object_group>(DEFORM_GROUP_DISABLED);
return current_group;
}
file_handler_->write<eOBJSyntaxElement::object_group>(
obj_mesh_data.get_poly_deform_group_name(current_group));
return current_group;
}
/**
* \return Writer function with appropriate polygon-element syntax.
*/
OBJWriter::func_vert_uv_normal_indices OBJWriter::get_poly_element_writer(
const int total_uv_vertices) const
{
if (export_params_.export_normals) {
if (export_params_.export_uv && (total_uv_vertices > 0)) {
/* Write both normals and UV indices. */
return &OBJWriter::write_vert_uv_normal_indices;
}
/* Write normals indices. */
return &OBJWriter::write_vert_normal_indices;
}
/* Write UV indices. */
if (export_params_.export_uv && (total_uv_vertices > 0)) {
return &OBJWriter::write_vert_uv_indices;
}
/* Write neither normals nor UV indices. */
return &OBJWriter::write_vert_indices;
}
/**
* Write polygon elements with at least vertex indices, and conditionally with UV vertex
* indices and polygon normal indices. Also write groups: smooth, vertex, material.
* The matname_fn turns a 0-indexed material slot number in an Object into the
* name used in the .obj file.
* \note UV indices were stored while writing UV vertices.
*/
void OBJWriter::write_poly_elements(const OBJMesh &obj_mesh_data,
std::function<const char *(int)> matname_fn)
{
int last_poly_smooth_group = NEGATIVE_INIT;
int16_t last_poly_vertex_group = NEGATIVE_INIT;
int16_t last_poly_mat_nr = NEGATIVE_INIT;
const func_vert_uv_normal_indices poly_element_writer = get_poly_element_writer(
obj_mesh_data.tot_uv_vertices());
/* Number of normals may not be equal to number of polygons due to smooth shading. */
int per_object_tot_normals = 0;
const int tot_polygons = obj_mesh_data.tot_polygons();
for (int i = 0; i < tot_polygons; i++) {
Vector<int> poly_vertex_indices = obj_mesh_data.calc_poly_vertex_indices(i);
Span<int> poly_uv_indices = obj_mesh_data.calc_poly_uv_indices(i);
/* For an Object, a normal index depends on how many of its normals have been written before
* it. This is unknown because of smooth shading. So pass "per object total normals"
* and update it after each call. */
int new_normals = 0;
Vector<int> poly_normal_indices;
std::tie(new_normals, poly_normal_indices) = obj_mesh_data.calc_poly_normal_indices(
i, per_object_tot_normals);
per_object_tot_normals += new_normals;
last_poly_smooth_group = write_smooth_group(obj_mesh_data, i, last_poly_smooth_group);
last_poly_vertex_group = write_vertex_group(obj_mesh_data, i, last_poly_vertex_group);
last_poly_mat_nr = write_poly_material(obj_mesh_data, i, last_poly_mat_nr, matname_fn);
(this->*poly_element_writer)(poly_vertex_indices, poly_uv_indices, poly_normal_indices);
}
/* Unusual: Other indices are updated in #OBJWriter::update_index_offsets. */
index_offsets_.normal_offset += per_object_tot_normals;
}
/**
* Write loose edges of a mesh as "l v1 v2".
*/
void OBJWriter::write_edges_indices(const OBJMesh &obj_mesh_data) const
{
obj_mesh_data.ensure_mesh_edges();
const int tot_edges = obj_mesh_data.tot_edges();
for (int edge_index = 0; edge_index < tot_edges; edge_index++) {
const std::optional<std::array<int, 2>> vertex_indices =
obj_mesh_data.calc_loose_edge_vert_indices(edge_index);
if (!vertex_indices) {
continue;
}
file_handler_->write<eOBJSyntaxElement::edge>(
(*vertex_indices)[0] + index_offsets_.vertex_offset + 1,
(*vertex_indices)[1] + index_offsets_.vertex_offset + 1);
}
}
/**
* Write a NURBS curve to the .OBJ file in parameter form.
*/
void OBJWriter::write_nurbs_curve(const OBJCurve &obj_nurbs_data) const
{
const int total_splines = obj_nurbs_data.total_splines();
for (int spline_idx = 0; spline_idx < total_splines; spline_idx++) {
const int total_vertices = obj_nurbs_data.total_spline_vertices(spline_idx);
for (int vertex_idx = 0; vertex_idx < total_vertices; vertex_idx++) {
const float3 vertex_coords = obj_nurbs_data.vertex_coordinates(
spline_idx, vertex_idx, export_params_.scaling_factor);
file_handler_->write<eOBJSyntaxElement::vertex_coords>(
vertex_coords[0], vertex_coords[1], vertex_coords[2]);
}
const char *nurbs_name = obj_nurbs_data.get_curve_name();
const int nurbs_degree = obj_nurbs_data.get_nurbs_degree(spline_idx);
file_handler_->write<eOBJSyntaxElement::object_group>(nurbs_name);
file_handler_->write<eOBJSyntaxElement::cstype>();
file_handler_->write<eOBJSyntaxElement::nurbs_degree>(nurbs_degree);
/**
* The numbers written here are indices into the vertex coordinates written
* earlier, relative to the line that is going to be written.
* [0.0 - 1.0] is the curve parameter range.
* 0.0 1.0 -1 -2 -3 -4 for a non-cyclic curve with 4 vertices.
* 0.0 1.0 -1 -2 -3 -4 -1 -2 -3 for a cyclic curve with 4 vertices.
*/
const int total_control_points = obj_nurbs_data.total_spline_control_points(spline_idx);
file_handler_->write<eOBJSyntaxElement::curve_element_begin>();
for (int i = 0; i < total_control_points; i++) {
/* "+1" to keep indices one-based, even if they're negative: i.e., -1 refers to the
* last vertex coordinate, -2 second last. */
file_handler_->write<eOBJSyntaxElement::vertex_indices>(-((i % total_vertices) + 1));
}
file_handler_->write<eOBJSyntaxElement::curve_element_end>();
/**
* In "parm u 0 0.1 .." line:, (total control points + 2) equidistant numbers in the
* parameter range are inserted.
*/
file_handler_->write<eOBJSyntaxElement::nurbs_parameter_begin>();
for (int i = 1; i <= total_control_points + 2; i++) {
file_handler_->write<eOBJSyntaxElement::nurbs_parameters>(1.0f * i /
(total_control_points + 2 + 1));
}
file_handler_->write<eOBJSyntaxElement::nurbs_parameter_end>();
file_handler_->write<eOBJSyntaxElement::nurbs_group_end>();
}
}
/**
* When there are multiple objects in a frame, the indices of previous objects' coordinates or
* normals add up.
*/
void OBJWriter::update_index_offsets(const OBJMesh &obj_mesh_data)
{
index_offsets_.vertex_offset += obj_mesh_data.tot_vertices();
index_offsets_.uv_vertex_offset += obj_mesh_data.tot_uv_vertices();
/* Normal index is updated right after writing the normals. */
}
/* -------------------------------------------------------------------- */
/** \name .MTL writers.
* \{ */
/**
* Convert #float3 to string of space-separated numbers, with no leading or trailing space.
* Only to be used in NON-performance-critical code.
*/
static std::string float3_to_string(const float3 &numbers)
{
std::ostringstream r_string;
r_string << numbers[0] << " " << numbers[1] << " " << numbers[2];
return r_string.str();
};
/*
* Create the .MTL file.
*/
MTLWriter::MTLWriter(const char *obj_filepath) noexcept(false)
{
mtl_filepath_ = obj_filepath;
const bool ok = BLI_path_extension_replace(mtl_filepath_.data(), FILE_MAX, ".mtl");
if (!ok) {
throw std::system_error(ENAMETOOLONG, std::system_category(), "");
}
file_handler_ = std::make_unique<FileHandler<eFileType::MTL>>(mtl_filepath_);
}
void MTLWriter::write_header(const char *blen_filepath) const
{
using namespace std::string_literals;
const char *blen_basename = (blen_filepath && blen_filepath[0] != '\0') ?
BLI_path_basename(blen_filepath) :
"None";
file_handler_->write<eMTLSyntaxElement::string>("# Blender "s + BKE_blender_version_string() +
" MTL File: '" + blen_basename + "'\n");
file_handler_->write<eMTLSyntaxElement::string>("# www.blender.org\n");
}
StringRefNull MTLWriter::mtl_file_path() const
{
return mtl_filepath_;
}
/**
* Write properties sourced from p-BSDF node or #Object.Material.
*/
void MTLWriter::write_bsdf_properties(const MTLMaterial &mtl_material)
{
file_handler_->write<eMTLSyntaxElement::Ns>(mtl_material.Ns);
file_handler_->write<eMTLSyntaxElement::Ka>(
mtl_material.Ka.x, mtl_material.Ka.y, mtl_material.Ka.z);
file_handler_->write<eMTLSyntaxElement::Kd>(
mtl_material.Kd.x, mtl_material.Kd.y, mtl_material.Kd.z);
file_handler_->write<eMTLSyntaxElement::Ks>(
mtl_material.Ks.x, mtl_material.Ks.y, mtl_material.Ks.z);
file_handler_->write<eMTLSyntaxElement::Ke>(
mtl_material.Ke.x, mtl_material.Ke.y, mtl_material.Ke.z);
file_handler_->write<eMTLSyntaxElement::Ni>(mtl_material.Ni);
file_handler_->write<eMTLSyntaxElement::d>(mtl_material.d);
file_handler_->write<eMTLSyntaxElement::illum>(mtl_material.illum);
}
/**
* Write a texture map in the form "map_XX -s 1. 1. 1. -o 0. 0. 0. [-bm 1.] path/to/image".
*/
void MTLWriter::write_texture_map(
const MTLMaterial &mtl_material,
const Map<const eMTLSyntaxElement, tex_map_XX>::Item &texture_map)
{
std::string translation;
std::string scale;
std::string map_bump_strength;
/* Optional strings should have their own leading spaces. */
if (texture_map.value.translation != float3{0.0f, 0.0f, 0.0f}) {
translation.append(" -s ").append(float3_to_string(texture_map.value.translation));
}
if (texture_map.value.scale != float3{1.0f, 1.0f, 1.0f}) {
scale.append(" -o ").append(float3_to_string(texture_map.value.scale));
}
if (texture_map.key == eMTLSyntaxElement::map_Bump && mtl_material.map_Bump_strength > 0.0001f) {
map_bump_strength.append(" -bm ").append(std::to_string(mtl_material.map_Bump_strength));
}
#define SYNTAX_DISPATCH(eMTLSyntaxElement) \
if (texture_map.key == eMTLSyntaxElement) { \
file_handler_->write<eMTLSyntaxElement>(translation + scale + map_bump_strength, \
texture_map.value.image_path); \
return; \
}
SYNTAX_DISPATCH(eMTLSyntaxElement::map_Kd);
SYNTAX_DISPATCH(eMTLSyntaxElement::map_Ks);
SYNTAX_DISPATCH(eMTLSyntaxElement::map_Ns);
SYNTAX_DISPATCH(eMTLSyntaxElement::map_d);
SYNTAX_DISPATCH(eMTLSyntaxElement::map_refl);
SYNTAX_DISPATCH(eMTLSyntaxElement::map_Ke);
SYNTAX_DISPATCH(eMTLSyntaxElement::map_Bump);
BLI_assert(!"This map type was not written to the file.");
}
/**
* Write all of the material specifications to the MTL file.
* For consistency of output from run to run (useful for testing),
* the materials are sorted by name before writing.
*/
void MTLWriter::write_materials()
{
if (mtlmaterials_.size() == 0) {
return;
}
std::sort(mtlmaterials_.begin(),
mtlmaterials_.end(),
[](const MTLMaterial &a, const MTLMaterial &b) { return a.name < b.name; });
for (const MTLMaterial &mtlmat : mtlmaterials_) {
file_handler_->write<eMTLSyntaxElement::string>("\n");
file_handler_->write<eMTLSyntaxElement::newmtl>(mtlmat.name);
write_bsdf_properties(mtlmat);
for (const Map<const eMTLSyntaxElement, tex_map_XX>::Item &texture_map :
mtlmat.texture_maps.items()) {
if (!texture_map.value.image_path.empty()) {
write_texture_map(mtlmat, texture_map);
}
}
}
}
/**
* Add the materials of the given object to MTLWriter, deduping
* against ones that are already there.
* Return a Vector of indices into mtlmaterials_ that hold the MTLMaterial
* that corresponds to each material slot, in order, of the given Object.
* Indexes are returned rather than pointers to the MTLMaterials themselves
* because the mtlmaterials_ Vector may move around when resized.
*/
Vector<int> MTLWriter::add_materials(const OBJMesh &mesh_to_export)
{
Vector<int> r_mtl_indices;
r_mtl_indices.resize(mesh_to_export.tot_materials());
for (int16_t i = 0; i < mesh_to_export.tot_materials(); i++) {
const Material *material = mesh_to_export.get_object_material(i);
if (!material) {
r_mtl_indices[i] = -1;
continue;
}
int mtlmat_index = material_map_.lookup_default(material, -1);
if (mtlmat_index != -1) {
r_mtl_indices[i] = mtlmat_index;
}
else {
mtlmaterials_.append(mtlmaterial_for_material(material));
r_mtl_indices[i] = mtlmaterials_.size() - 1;
material_map_.add_new(material, r_mtl_indices[i]);
}
}
return r_mtl_indices;
}
const char *MTLWriter::mtlmaterial_name(int index)
{
if (index < 0 || index >= mtlmaterials_.size()) {
return nullptr;
}
return mtlmaterials_[index].name.c_str();
}
/** \} */
} // namespace blender::io::obj

View File

@@ -0,0 +1,135 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup obj
*/
#pragma once
#include "DNA_meshdata_types.h"
#include "BLI_map.hh"
#include "BLI_vector.hh"
#include "IO_wavefront_obj.h"
#include "obj_export_io.hh"
#include "obj_export_mtl.hh"
namespace blender::io::obj {
class OBJCurve;
class OBJMesh;
/**
* Total vertices/ UV vertices/ normals of previous Objects
* should be added to the current Object's indices.
*/
struct IndexOffsets {
int vertex_offset;
int uv_vertex_offset;
int normal_offset;
};
/**
* Responsible for writing a .OBJ file.
*/
class OBJWriter : NonMovable, NonCopyable {
private:
const OBJExportParams &export_params_;
std::unique_ptr<FileHandler<eFileType::OBJ>> file_handler_ = nullptr;
IndexOffsets index_offsets_{0, 0, 0};
public:
OBJWriter(const char *filepath, const OBJExportParams &export_params) noexcept(false)
: export_params_(export_params)
{
file_handler_ = std::make_unique<FileHandler<eFileType::OBJ>>(filepath);
}
void write_header() const;
void write_object_name(const OBJMesh &obj_mesh_data) const;
void write_object_group(const OBJMesh &obj_mesh_data) const;
void write_mtllib_name(const StringRefNull mtl_filepath) const;
void write_vertex_coords(const OBJMesh &obj_mesh_data) const;
void write_uv_coords(OBJMesh &obj_mesh_data) const;
void write_poly_normals(const OBJMesh &obj_mesh_data) const;
int write_smooth_group(const OBJMesh &obj_mesh_data,
int poly_index,
const int last_poly_smooth_group) const;
int16_t write_poly_material(const OBJMesh &obj_mesh_data,
const int poly_index,
const int16_t last_poly_mat_nr,
std::function<const char *(int)> matname_fn) const;
int16_t write_vertex_group(const OBJMesh &obj_mesh_data,
const int poly_index,
const int16_t last_poly_vertex_group) const;
void write_poly_elements(const OBJMesh &obj_mesh_data,
std::function<const char *(int)> matname_fn);
void write_edges_indices(const OBJMesh &obj_mesh_data) const;
void write_nurbs_curve(const OBJCurve &obj_nurbs_data) const;
void update_index_offsets(const OBJMesh &obj_mesh_data);
private:
using func_vert_uv_normal_indices = void (OBJWriter::*)(Span<int> vert_indices,
Span<int> uv_indices,
Span<int> normal_indices) const;
func_vert_uv_normal_indices get_poly_element_writer(const int total_uv_vertices) const;
void write_vert_uv_normal_indices(Span<int> vert_indices,
Span<int> uv_indices,
Span<int> normal_indices) const;
void write_vert_normal_indices(Span<int> vert_indices,
Span<int> /*uv_indices*/,
Span<int> normal_indices) const;
void write_vert_uv_indices(Span<int> vert_indices,
Span<int> uv_indices,
Span<int> /*normal_indices*/) const;
void write_vert_indices(Span<int> vert_indices,
Span<int> /*uv_indices*/,
Span<int> /*normal_indices*/) const;
};
/**
* Responsible for writing a .MTL file.
*/
class MTLWriter : NonMovable, NonCopyable {
private:
std::unique_ptr<FileHandler<eFileType::MTL>> file_handler_ = nullptr;
std::string mtl_filepath_;
Vector<MTLMaterial> mtlmaterials_;
/* Map from a Material* to an index into mtlmaterials_. */
Map<const Material *, int> material_map_;
public:
MTLWriter(const char *obj_filepath) noexcept(false);
void write_header(const char *blen_filepath) const;
void write_materials();
StringRefNull mtl_file_path() const;
Vector<int> add_materials(const OBJMesh &mesh_to_export);
const char *mtlmaterial_name(int index);
private:
void write_bsdf_properties(const MTLMaterial &mtl_material);
void write_texture_map(const MTLMaterial &mtl_material,
const Map<const eMTLSyntaxElement, tex_map_XX>::Item &texture_map);
};
} // namespace blender::io::obj

View File

@@ -0,0 +1,343 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup obj
*/
#pragma once
#include <cstdio>
#include <string>
#include <system_error>
#include <type_traits>
#include "BLI_compiler_attrs.h"
#include "BLI_string_ref.hh"
#include "BLI_utility_mixins.hh"
namespace blender::io::obj {
enum class eFileType {
OBJ,
MTL,
};
enum class eOBJSyntaxElement {
vertex_coords,
uv_vertex_coords,
normal,
poly_element_begin,
vertex_uv_normal_indices,
vertex_normal_indices,
vertex_uv_indices,
vertex_indices,
poly_element_end,
poly_usemtl,
edge,
cstype,
nurbs_degree,
curve_element_begin,
curve_element_end,
nurbs_parameter_begin,
nurbs_parameters,
nurbs_parameter_end,
nurbs_group_end,
new_line,
mtllib,
smooth_group,
object_group,
object_name,
/* Use rarely. New line is NOT included for string. */
string,
};
enum class eMTLSyntaxElement {
newmtl,
Ni,
d,
Ns,
illum,
Ka,
Kd,
Ks,
Ke,
map_Kd,
map_Ks,
map_Ns,
map_d,
map_refl,
map_Ke,
map_Bump,
/* Use rarely. New line is NOT included for string. */
string,
};
template<eFileType filetype> struct FileTypeTraits;
template<> struct FileTypeTraits<eFileType::OBJ> {
using SyntaxType = eOBJSyntaxElement;
};
template<> struct FileTypeTraits<eFileType::MTL> {
using SyntaxType = eMTLSyntaxElement;
};
template<eFileType type> struct Formatting {
const char *fmt = nullptr;
const int total_args = 0;
/* Fail to compile by default. */
const bool is_type_valid = false;
};
/**
* Type dependent but always false. Use to add a conditional compile-time error.
*/
template<typename T> struct always_false : std::false_type {
};
template<typename... T>
constexpr bool is_type_float = (... && std::is_floating_point_v<std::decay_t<T>>);
template<typename... T>
constexpr bool is_type_integral = (... && std::is_integral_v<std::decay_t<T>>);
template<typename... T>
constexpr bool is_type_string_related = (... && std::is_constructible_v<std::string, T>);
template<eFileType filetype, typename... T>
constexpr std::enable_if_t<filetype == eFileType::OBJ, Formatting<filetype>>
syntax_elem_to_formatting(const eOBJSyntaxElement key)
{
switch (key) {
case eOBJSyntaxElement::vertex_coords: {
return {"v %f %f %f\n", 3, is_type_float<T...>};
}
case eOBJSyntaxElement::uv_vertex_coords: {
return {"vt %f %f\n", 2, is_type_float<T...>};
}
case eOBJSyntaxElement::normal: {
return {"vn %f %f %f\n", 3, is_type_float<T...>};
}
case eOBJSyntaxElement::poly_element_begin: {
return {"f", 0, is_type_string_related<T...>};
}
case eOBJSyntaxElement::vertex_uv_normal_indices: {
return {" %d/%d/%d", 3, is_type_integral<T...>};
}
case eOBJSyntaxElement::vertex_normal_indices: {
return {" %d//%d", 2, is_type_integral<T...>};
}
case eOBJSyntaxElement::vertex_uv_indices: {
return {" %d/%d", 2, is_type_integral<T...>};
}
case eOBJSyntaxElement::vertex_indices: {
return {" %d", 1, is_type_integral<T...>};
}
case eOBJSyntaxElement::poly_usemtl: {
return {"usemtl %s\n", 1, is_type_string_related<T...>};
}
case eOBJSyntaxElement::edge: {
return {"l %d %d\n", 2, is_type_integral<T...>};
}
case eOBJSyntaxElement::cstype: {
return {"cstype bspline\n", 0, is_type_string_related<T...>};
}
case eOBJSyntaxElement::nurbs_degree: {
return {"deg %d\n", 1, is_type_integral<T...>};
}
case eOBJSyntaxElement::curve_element_begin: {
return {"curv 0.0 1.0", 0, is_type_string_related<T...>};
}
case eOBJSyntaxElement::nurbs_parameter_begin: {
return {"parm 0.0", 0, is_type_string_related<T...>};
}
case eOBJSyntaxElement::nurbs_parameters: {
return {" %f", 1, is_type_float<T...>};
}
case eOBJSyntaxElement::nurbs_parameter_end: {
return {" 1.0\n", 0, is_type_string_related<T...>};
}
case eOBJSyntaxElement::nurbs_group_end: {
return {"end\n", 0, is_type_string_related<T...>};
}
case eOBJSyntaxElement::poly_element_end: {
ATTR_FALLTHROUGH;
}
case eOBJSyntaxElement::curve_element_end: {
ATTR_FALLTHROUGH;
}
case eOBJSyntaxElement::new_line: {
return {"\n", 0, is_type_string_related<T...>};
}
case eOBJSyntaxElement::mtllib: {
return {"mtllib %s\n", 1, is_type_string_related<T...>};
}
case eOBJSyntaxElement::smooth_group: {
return {"s %d\n", 1, is_type_integral<T...>};
}
case eOBJSyntaxElement::object_group: {
return {"g %s\n", 1, is_type_string_related<T...>};
}
case eOBJSyntaxElement::object_name: {
return {"o %s\n", 1, is_type_string_related<T...>};
}
case eOBJSyntaxElement::string: {
return {"%s", 1, is_type_string_related<T...>};
}
}
}
template<eFileType filetype, typename... T>
constexpr std::enable_if_t<filetype == eFileType::MTL, Formatting<filetype>>
syntax_elem_to_formatting(const eMTLSyntaxElement key)
{
switch (key) {
case eMTLSyntaxElement::newmtl: {
return {"newmtl %s\n", 1, is_type_string_related<T...>};
}
case eMTLSyntaxElement::Ni: {
return {"Ni %.6f\n", 1, is_type_float<T...>};
}
case eMTLSyntaxElement::d: {
return {"d %.6f\n", 1, is_type_float<T...>};
}
case eMTLSyntaxElement::Ns: {
return {"Ns %.6f\n", 1, is_type_float<T...>};
}
case eMTLSyntaxElement::illum: {
return {"illum %d\n", 1, is_type_integral<T...>};
}
case eMTLSyntaxElement::Ka: {
return {"Ka %.6f %.6f %.6f\n", 3, is_type_float<T...>};
}
case eMTLSyntaxElement::Kd: {
return {"Kd %.6f %.6f %.6f\n", 3, is_type_float<T...>};
}
case eMTLSyntaxElement::Ks: {
return {"Ks %.6f %.6f %.6f\n", 3, is_type_float<T...>};
}
case eMTLSyntaxElement::Ke: {
return {"Ke %.6f %.6f %.6f\n", 3, is_type_float<T...>};
}
/* Keep only one space between options since filepaths may have leading spaces too. */
case eMTLSyntaxElement::map_Kd: {
return {"map_Kd %s %s\n", 2, is_type_string_related<T...>};
}
case eMTLSyntaxElement::map_Ks: {
return {"map_Ks %s %s\n", 2, is_type_string_related<T...>};
}
case eMTLSyntaxElement::map_Ns: {
return {"map_Ns %s %s\n", 2, is_type_string_related<T...>};
}
case eMTLSyntaxElement::map_d: {
return {"map_d %s %s\n", 2, is_type_string_related<T...>};
}
case eMTLSyntaxElement::map_refl: {
return {"map_refl %s %s\n", 2, is_type_string_related<T...>};
}
case eMTLSyntaxElement::map_Ke: {
return {"map_Ke %s %s\n", 2, is_type_string_related<T...>};
}
case eMTLSyntaxElement::map_Bump: {
return {"map_Bump %s %s\n", 2, is_type_string_related<T...>};
}
case eMTLSyntaxElement::string: {
return {"%s", 1, is_type_string_related<T...>};
}
}
}
template<eFileType filetype> class FileHandler : NonCopyable, NonMovable {
private:
FILE *outfile_ = nullptr;
std::string outfile_path_;
public:
FileHandler(std::string outfile_path) noexcept(false) : outfile_path_(std::move(outfile_path))
{
outfile_ = std::fopen(outfile_path_.c_str(), "w");
if (!outfile_) {
throw std::system_error(errno, std::system_category(), "Cannot open file");
}
}
~FileHandler()
{
if (outfile_ && std::fclose(outfile_)) {
std::cerr << "Error: could not close the file '" << outfile_path_
<< "' properly, it may be corrupted." << std::endl;
}
}
template<typename FileTypeTraits<filetype>::SyntaxType key, typename... T>
constexpr void write(T &&...args) const
{
constexpr Formatting<filetype> fmt_nargs_valid = syntax_elem_to_formatting<filetype, T...>(
key);
write__impl<fmt_nargs_valid.total_args>(fmt_nargs_valid.fmt, std::forward<T>(args)...);
/* Types of all arguments and the number of arguments should match
* what the formatting specifies. */
return std::enable_if_t < fmt_nargs_valid.is_type_valid &&
(sizeof...(T) == fmt_nargs_valid.total_args),
void > ();
}
private:
/* Remove this after upgrading to C++20. */
template<typename T> using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<T>>;
/**
* Make #std::string etc., usable for fprintf-family.
* \return: `const char *` or the original argument if the argument is
* not related to #std::string.
*/
template<typename T> constexpr auto string_to_primitive(T &&arg) const
{
if constexpr (std::is_same_v<remove_cvref_t<T>, std::string> ||
std::is_same_v<remove_cvref_t<T>, blender::StringRefNull>) {
return arg.c_str();
}
else if constexpr (std::is_same_v<remove_cvref_t<T>, blender::StringRef>) {
BLI_STATIC_ASSERT(
(always_false<T>::value),
"Null-terminated string not present. Please use blender::StringRefNull instead.");
/* Another trick to cause a compile-time error: returning nothing to #std::printf. */
return;
}
else {
return std::forward<T>(arg);
}
}
template<int total_args, typename... T>
constexpr std::enable_if_t<(total_args != 0), void> write__impl(const char *fmt,
T &&...args) const
{
std::fprintf(outfile_, fmt, string_to_primitive(std::forward<T>(args))...);
}
template<int total_args, typename... T>
constexpr std::enable_if_t<(total_args == 0), void> write__impl(const char *fmt,
T &&...args) const
{
std::fputs(fmt, outfile_);
}
};
} // namespace blender::io::obj

View File

@@ -0,0 +1,490 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup obj
*/
#include "BKE_customdata.h"
#include "BKE_lib_id.h"
#include "BKE_material.h"
#include "BKE_mesh.h"
#include "BKE_mesh_mapping.h"
#include "BKE_object.h"
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "DEG_depsgraph_query.h"
#include "DNA_material_types.h"
#include "DNA_mesh_types.h"
#include "DNA_modifier_types.h"
#include "DNA_object_types.h"
#include "obj_export_mesh.hh"
namespace blender::io::obj {
/**
* Store evaluated Object and Mesh pointers. Conditionally triangulate a mesh, or
* create a new Mesh from a Curve.
*/
OBJMesh::OBJMesh(Depsgraph *depsgraph, const OBJExportParams &export_params, Object *mesh_object)
{
export_object_eval_ = DEG_get_evaluated_object(depsgraph, mesh_object);
export_mesh_eval_ = BKE_object_get_evaluated_mesh(export_object_eval_);
mesh_eval_needs_free_ = false;
if (!export_mesh_eval_) {
/* Curves and NURBS surfaces need a new mesh when they're
* exported in the form of vertices and edges.
*/
export_mesh_eval_ = BKE_mesh_new_from_object(depsgraph, export_object_eval_, true, true);
/* Since a new mesh been allocated, it needs to be freed in the destructor. */
mesh_eval_needs_free_ = true;
}
if (export_params.export_triangulated_mesh &&
ELEM(export_object_eval_->type, OB_MESH, OB_SURF)) {
std::tie(export_mesh_eval_, mesh_eval_needs_free_) = triangulate_mesh_eval();
}
set_world_axes_transform(export_params.forward_axis, export_params.up_axis);
}
/**
* Free new meshes allocated for triangulated meshes, or Curve converted to Mesh.
*/
OBJMesh::~OBJMesh()
{
free_mesh_if_needed();
if (poly_smooth_groups_) {
MEM_freeN(poly_smooth_groups_);
}
}
/**
* Free the mesh if _the exporter_ created it.
*/
void OBJMesh::free_mesh_if_needed()
{
if (mesh_eval_needs_free_ && export_mesh_eval_) {
BKE_id_free(nullptr, export_mesh_eval_);
}
}
/**
* Allocate a new Mesh with triangulated polygons.
*
* The returned mesh can be the same as the old one.
* \return Owning pointer to the new Mesh, and whether a new Mesh was created.
*/
std::pair<Mesh *, bool> OBJMesh::triangulate_mesh_eval()
{
if (export_mesh_eval_->totpoly <= 0) {
return {export_mesh_eval_, false};
}
const struct BMeshCreateParams bm_create_params = {0u};
const struct BMeshFromMeshParams bm_convert_params = {1u, 0, 0, 0};
/* Lower threshold where triangulation of a polygon starts, i.e. a quadrilateral will be
* triangulated here. */
const int triangulate_min_verts = 4;
unique_bmesh_ptr bmesh(
BKE_mesh_to_bmesh_ex(export_mesh_eval_, &bm_create_params, &bm_convert_params));
BM_mesh_triangulate(bmesh.get(),
MOD_TRIANGULATE_NGON_BEAUTY,
MOD_TRIANGULATE_QUAD_SHORTEDGE,
triangulate_min_verts,
false,
nullptr,
nullptr,
nullptr);
Mesh *triangulated = BKE_mesh_from_bmesh_for_eval_nomain(
bmesh.get(), nullptr, export_mesh_eval_);
free_mesh_if_needed();
return {triangulated, true};
}
/**
* Set the final transform after applying axes settings and an Object's world transform.
*/
void OBJMesh::set_world_axes_transform(const eTransformAxisForward forward,
const eTransformAxisUp up)
{
float axes_transform[3][3];
unit_m3(axes_transform);
/* +Y-forward and +Z-up are the default Blender axis settings. */
mat3_from_axis_conversion(OBJ_AXIS_Y_FORWARD, OBJ_AXIS_Z_UP, forward, up, axes_transform);
/* mat3_from_axis_conversion returns a transposed matrix! */
transpose_m3(axes_transform);
mul_m4_m3m4(world_and_axes_transform_, axes_transform, export_object_eval_->obmat);
/* mul_m4_m3m4 does not transform last row of obmat, i.e. location data. */
mul_v3_m3v3(world_and_axes_transform_[3], axes_transform, export_object_eval_->obmat[3]);
world_and_axes_transform_[3][3] = export_object_eval_->obmat[3][3];
}
int OBJMesh::tot_vertices() const
{
return export_mesh_eval_->totvert;
}
int OBJMesh::tot_polygons() const
{
return export_mesh_eval_->totpoly;
}
int OBJMesh::tot_uv_vertices() const
{
return tot_uv_vertices_;
}
int OBJMesh::tot_edges() const
{
return export_mesh_eval_->totedge;
}
/**
* \return Total materials in the object.
*/
int16_t OBJMesh::tot_materials() const
{
return export_mesh_eval_->totcol;
}
/**
* \return Smooth group of the polygon at the given index.
*/
int OBJMesh::ith_smooth_group(const int poly_index) const
{
/* Calculate smooth groups first: #OBJMesh::calc_smooth_groups. */
BLI_assert(tot_smooth_groups_ != -NEGATIVE_INIT);
BLI_assert(poly_smooth_groups_);
return poly_smooth_groups_[poly_index];
}
void OBJMesh::ensure_mesh_normals() const
{
BKE_mesh_ensure_normals(export_mesh_eval_);
BKE_mesh_calc_normals_split(export_mesh_eval_);
}
void OBJMesh::ensure_mesh_edges() const
{
BKE_mesh_calc_edges(export_mesh_eval_, true, false);
BKE_mesh_calc_edges_loose(export_mesh_eval_);
}
/**
* Calculate smooth groups of a smooth-shaded object.
* \return A polygon aligned array of smooth group numbers.
*/
void OBJMesh::calc_smooth_groups(const bool use_bitflags)
{
poly_smooth_groups_ = BKE_mesh_calc_smoothgroups(export_mesh_eval_->medge,
export_mesh_eval_->totedge,
export_mesh_eval_->mpoly,
export_mesh_eval_->totpoly,
export_mesh_eval_->mloop,
export_mesh_eval_->totloop,
&tot_smooth_groups_,
use_bitflags);
}
/**
* Return mat_nr-th material of the object. The given index should be zero-based.
*/
const Material *OBJMesh::get_object_material(const int16_t mat_nr) const
{
/* "+ 1" as material getter needs one-based indices. */
const Material *r_mat = BKE_object_material_get(export_object_eval_, mat_nr + 1);
#ifdef DEBUG
if (!r_mat) {
std::cerr << "Material not found for mat_nr = " << mat_nr << std::endl;
}
#endif
return r_mat;
}
bool OBJMesh::is_ith_poly_smooth(const int poly_index) const
{
return export_mesh_eval_->mpoly[poly_index].flag & ME_SMOOTH;
}
/**
* Returns a zero-based index of a polygon's material indexing into
* the Object's material slots.
*/
int16_t OBJMesh::ith_poly_matnr(const int poly_index) const
{
BLI_assert(poly_index < export_mesh_eval_->totpoly);
const int16_t r_mat_nr = export_mesh_eval_->mpoly[poly_index].mat_nr;
return r_mat_nr >= 0 ? r_mat_nr : NOT_FOUND;
}
/**
* Get object name as it appears in the outliner.
*/
const char *OBJMesh::get_object_name() const
{
return export_object_eval_->id.name + 2;
}
/**
* Get Object's Mesh's name.
*/
const char *OBJMesh::get_object_mesh_name() const
{
return export_mesh_eval_->id.name + 2;
}
/**
* Get object's material (at the given index) name. The given index should be zero-based.
*/
const char *OBJMesh::get_object_material_name(const int16_t mat_nr) const
{
const Material *mat = get_object_material(mat_nr);
if (!mat) {
return nullptr;
}
return mat->id.name + 2;
}
/**
* Calculate coordinates of the vertex at the given index.
*/
float3 OBJMesh::calc_vertex_coords(const int vert_index, const float scaling_factor) const
{
float3 r_coords;
copy_v3_v3(r_coords, export_mesh_eval_->mvert[vert_index].co);
mul_v3_fl(r_coords, scaling_factor);
mul_m4_v3(world_and_axes_transform_, r_coords);
return r_coords;
}
/**
* Calculate vertex indices of all vertices of the polygon at the given index.
*/
Vector<int> OBJMesh::calc_poly_vertex_indices(const int poly_index) const
{
const MPoly &mpoly = export_mesh_eval_->mpoly[poly_index];
const MLoop *mloop = &export_mesh_eval_->mloop[mpoly.loopstart];
const int totloop = mpoly.totloop;
Vector<int> r_poly_vertex_indices(totloop);
for (int loop_index = 0; loop_index < totloop; loop_index++) {
r_poly_vertex_indices[loop_index] = mloop[loop_index].v;
}
return r_poly_vertex_indices;
}
/**
* Calculate UV vertex coordinates of an Object.
*
* \note Also store the UV vertex indices in the member variable.
*/
void OBJMesh::store_uv_coords_and_indices(Vector<std::array<float, 2>> &r_uv_coords)
{
const MPoly *mpoly = export_mesh_eval_->mpoly;
const MLoop *mloop = export_mesh_eval_->mloop;
const int totpoly = export_mesh_eval_->totpoly;
const int totvert = export_mesh_eval_->totvert;
const MLoopUV *mloopuv = static_cast<MLoopUV *>(
CustomData_get_layer(&export_mesh_eval_->ldata, CD_MLOOPUV));
if (!mloopuv) {
tot_uv_vertices_ = 0;
return;
}
const float limit[2] = {STD_UV_CONNECT_LIMIT, STD_UV_CONNECT_LIMIT};
UvVertMap *uv_vert_map = BKE_mesh_uv_vert_map_create(
mpoly, mloop, mloopuv, totpoly, totvert, limit, false, false);
uv_indices_.resize(totpoly);
/* At least total vertices of a mesh will be present in its texture map. So
* reserve minimum space early. */
r_uv_coords.reserve(totvert);
tot_uv_vertices_ = 0;
for (int vertex_index = 0; vertex_index < totvert; vertex_index++) {
const UvMapVert *uv_vert = BKE_mesh_uv_vert_map_get_vert(uv_vert_map, vertex_index);
for (; uv_vert; uv_vert = uv_vert->next) {
if (uv_vert->separate) {
tot_uv_vertices_ += 1;
}
const int vertices_in_poly = mpoly[uv_vert->poly_index].totloop;
/* Store UV vertex coordinates. */
r_uv_coords.resize(tot_uv_vertices_);
const int loopstart = mpoly[uv_vert->poly_index].loopstart;
Span<float> vert_uv_coords(mloopuv[loopstart + uv_vert->loop_of_poly_index].uv, 2);
r_uv_coords[tot_uv_vertices_ - 1][0] = vert_uv_coords[0];
r_uv_coords[tot_uv_vertices_ - 1][1] = vert_uv_coords[1];
/* Store UV vertex indices. */
uv_indices_[uv_vert->poly_index].resize(vertices_in_poly);
/* Keep indices zero-based and let the writer handle the "+ 1" as per OBJ spec. */
uv_indices_[uv_vert->poly_index][uv_vert->loop_of_poly_index] = tot_uv_vertices_ - 1;
}
}
BKE_mesh_uv_vert_map_free(uv_vert_map);
}
Span<int> OBJMesh::calc_poly_uv_indices(const int poly_index) const
{
if (uv_indices_.size() <= 0) {
return {};
}
BLI_assert(poly_index < export_mesh_eval_->totpoly);
BLI_assert(poly_index < uv_indices_.size());
return uv_indices_[poly_index];
}
/**
* Calculate polygon normal of a polygon at given index.
*
* Should be used for flat-shaded polygons.
*/
float3 OBJMesh::calc_poly_normal(const int poly_index) const
{
float3 r_poly_normal;
const MPoly &poly = export_mesh_eval_->mpoly[poly_index];
const MLoop &mloop = export_mesh_eval_->mloop[poly.loopstart];
const MVert &mvert = *(export_mesh_eval_->mvert);
BKE_mesh_calc_poly_normal(&poly, &mloop, &mvert, r_poly_normal);
mul_mat3_m4_v3(world_and_axes_transform_, r_poly_normal);
return r_poly_normal;
}
/**
* Calculate loop normals of a polygon at the given index.
*
* Should be used for smooth-shaded polygons.
*/
void OBJMesh::calc_loop_normals(const int poly_index, Vector<float3> &r_loop_normals) const
{
r_loop_normals.clear();
const MPoly &mpoly = export_mesh_eval_->mpoly[poly_index];
const float(
*lnors)[3] = (const float(*)[3])(CustomData_get_layer(&export_mesh_eval_->ldata, CD_NORMAL));
for (int loop_of_poly = 0; loop_of_poly < mpoly.totloop; loop_of_poly++) {
float3 loop_normal;
copy_v3_v3(loop_normal, lnors[mpoly.loopstart + loop_of_poly]);
mul_mat3_m4_v3(world_and_axes_transform_, loop_normal);
r_loop_normals.append(loop_normal);
}
}
/**
* Calculate a polygon's polygon/loop normal indices.
* \param object_tot_prev_normals Number of normals of this Object written so far.
* \return Number of distinct normal indices.
*/
std::pair<int, Vector<int>> OBJMesh::calc_poly_normal_indices(
const int poly_index, const int object_tot_prev_normals) const
{
const MPoly &mpoly = export_mesh_eval_->mpoly[poly_index];
const int totloop = mpoly.totloop;
Vector<int> r_poly_normal_indices(totloop);
if (is_ith_poly_smooth(poly_index)) {
for (int poly_loop_index = 0; poly_loop_index < totloop; poly_loop_index++) {
/* Using polygon loop index is fine because polygon/loop normals and their normal indices are
* written by looping over #Mesh.mpoly /#Mesh.mloop in the same order. */
r_poly_normal_indices[poly_loop_index] = object_tot_prev_normals + poly_loop_index;
}
/* For a smooth-shaded polygon, #Mesh.totloop -many loop normals are written. */
return {totloop, r_poly_normal_indices};
}
for (int poly_loop_index = 0; poly_loop_index < totloop; poly_loop_index++) {
r_poly_normal_indices[poly_loop_index] = object_tot_prev_normals;
}
/* For a flat-shaded polygon, one polygon normal is written. */
return {1, r_poly_normal_indices};
}
/**
* Find the index of the vertex group with the maximum number of vertices in a polygon.
* The index indices into the #Object.defbase.
*
* If two or more groups have the same number of vertices (maximum), group name depends on the
* implementation of #std::max_element.
*/
int16_t OBJMesh::get_poly_deform_group_index(const int poly_index) const
{
BLI_assert(poly_index < export_mesh_eval_->totpoly);
const MPoly &mpoly = export_mesh_eval_->mpoly[poly_index];
const MLoop *mloop = &export_mesh_eval_->mloop[mpoly.loopstart];
const int tot_deform_groups = BLI_listbase_count(&export_object_eval_->defbase);
/* Indices of the vector index into deform groups of an object; values are the]
* number of vertex members in one deform group. */
Vector<int16_t> deform_group_members(tot_deform_groups, 0);
/* Whether at least one vertex in the polygon belongs to any group. */
bool found_group = false;
const MDeformVert *dvert_orig = static_cast<MDeformVert *>(
CustomData_get_layer(&export_mesh_eval_->vdata, CD_MDEFORMVERT));
if (!dvert_orig) {
return NOT_FOUND;
}
const MDeformWeight *curr_weight = nullptr;
const MDeformVert *dvert = nullptr;
for (int loop_index = 0; loop_index < mpoly.totloop; loop_index++) {
dvert = &dvert_orig[(mloop + loop_index)->v];
curr_weight = dvert->dw;
if (curr_weight) {
bDeformGroup *vertex_group = static_cast<bDeformGroup *>(
BLI_findlink((&export_object_eval_->defbase), curr_weight->def_nr));
if (vertex_group) {
deform_group_members[curr_weight->def_nr] += 1;
found_group = true;
}
}
}
if (!found_group) {
return NOT_FOUND;
}
/* Index of the group with maximum vertices. */
int16_t max_idx = std::max_element(deform_group_members.begin(), deform_group_members.end()) -
deform_group_members.begin();
return max_idx;
}
/**
* Find the name of the vertex deform group at the given index.
* The index indices into the #Object.defbase.
*/
const char *OBJMesh::get_poly_deform_group_name(const int16_t def_group_index) const
{
const bDeformGroup &vertex_group = *(
static_cast<bDeformGroup *>(BLI_findlink(&export_object_eval_->defbase, def_group_index)));
return vertex_group.name;
}
/**
* Calculate vertex indices of an edge's corners if it is a loose edge.
*/
std::optional<std::array<int, 2>> OBJMesh::calc_loose_edge_vert_indices(const int edge_index) const
{
const MEdge &edge = export_mesh_eval_->medge[edge_index];
if (edge.flag & ME_LOOSEEDGE) {
return std::array<int, 2>{static_cast<int>(edge.v1), static_cast<int>(edge.v2)};
}
return std::nullopt;
}
} // namespace blender::io::obj

View File

@@ -0,0 +1,134 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup obj
*/
#pragma once
#include <optional>
#include "BLI_float3.hh"
#include "BLI_utility_mixins.hh"
#include "BLI_vector.hh"
#include "bmesh.h"
#include "bmesh_tools.h"
#include "DNA_material_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "IO_wavefront_obj.h"
namespace blender::io::obj {
/* Denote absence for usually non-negative numbers. */
const int NOT_FOUND = -1;
/* Any negative number other than `NOT_FOUND` to initialise usually non-negative numbers. */
const int NEGATIVE_INIT = -10;
/**
* #std::unique_ptr deleter for BMesh.
*/
struct CustomBMeshDeleter {
void operator()(BMesh *bmesh)
{
if (bmesh) {
BM_mesh_free(bmesh);
}
}
};
using unique_bmesh_ptr = std::unique_ptr<BMesh, CustomBMeshDeleter>;
class OBJMesh : NonCopyable {
private:
Object *export_object_eval_;
Mesh *export_mesh_eval_;
/**
* For curves which are converted to mesh, and triangulated meshes, a new mesh is allocated.
*/
bool mesh_eval_needs_free_ = false;
/**
* Final transform of an object obtained from export settings (up_axis, forward_axis) and the
* object's world transform matrix.
*/
float world_and_axes_transform_[4][4];
/**
* Total UV vertices in a mesh's texture map.
*/
int tot_uv_vertices_ = 0;
/**
* Per-polygon-per-vertex UV vertex indices.
*/
Vector<Vector<int>> uv_indices_;
/**
* Total smooth groups in an object.
*/
int tot_smooth_groups_ = NEGATIVE_INIT;
/**
* Polygon aligned array of their smooth groups.
*/
int *poly_smooth_groups_ = nullptr;
public:
OBJMesh(Depsgraph *depsgraph, const OBJExportParams &export_params, Object *mesh_object);
~OBJMesh();
int tot_vertices() const;
int tot_polygons() const;
int tot_uv_vertices() const;
int tot_edges() const;
int16_t tot_materials() const;
const Material *get_object_material(const int16_t mat_nr) const;
int16_t ith_poly_matnr(const int poly_index) const;
void ensure_mesh_normals() const;
void ensure_mesh_edges() const;
void calc_smooth_groups(const bool use_bitflags);
int ith_smooth_group(const int poly_index) const;
bool is_ith_poly_smooth(const int poly_index) const;
const char *get_object_name() const;
const char *get_object_mesh_name() const;
const char *get_object_material_name(const int16_t mat_nr) const;
float3 calc_vertex_coords(const int vert_index, const float scaling_factor) const;
Vector<int> calc_poly_vertex_indices(const int poly_index) const;
void store_uv_coords_and_indices(Vector<std::array<float, 2>> &r_uv_coords);
Span<int> calc_poly_uv_indices(const int poly_index) const;
float3 calc_poly_normal(const int poly_index) const;
std::pair<int, Vector<int>> calc_poly_normal_indices(const int poly_index,
const int object_tot_prev_normals) const;
void calc_loop_normals(const int poly_index, Vector<float3> &r_loop_normals) const;
int16_t get_poly_deform_group_index(const int poly_index) const;
const char *get_poly_deform_group_name(const int16_t def_group_index) const;
std::optional<std::array<int, 2>> calc_loose_edge_vert_indices(const int edge_index) const;
private:
void free_mesh_if_needed();
std::pair<Mesh *, bool> triangulate_mesh_eval();
void set_world_axes_transform(const eTransformAxisForward forward, const eTransformAxisUp up);
};
} // namespace blender::io::obj

View File

@@ -0,0 +1,365 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup obj
*/
#include "BKE_image.h"
#include "BKE_node.h"
#include "BLI_float3.hh"
#include "BLI_map.hh"
#include "BLI_path_util.h"
#include "DNA_material_types.h"
#include "DNA_node_types.h"
#include "NOD_node_tree_ref.hh"
#include "obj_export_mesh.hh"
#include "obj_export_mtl.hh"
namespace blender::io::obj {
/**
* Copy a float property of the given type from the bNode to given buffer.
*/
static void copy_property_from_node(const eNodeSocketDatatype property_type,
const bNode *node,
const char *identifier,
MutableSpan<float> r_property)
{
if (!node) {
return;
}
bNodeSocket *socket{nodeFindSocket(node, SOCK_IN, identifier)};
BLI_assert(socket && socket->type == property_type);
if (!socket) {
return;
}
switch (property_type) {
case SOCK_FLOAT: {
BLI_assert(r_property.size() == 1);
bNodeSocketValueFloat *socket_def_value = static_cast<bNodeSocketValueFloat *>(
socket->default_value);
r_property[0] = socket_def_value->value;
break;
}
case SOCK_RGBA: {
BLI_assert(r_property.size() == 3);
bNodeSocketValueRGBA *socket_def_value = static_cast<bNodeSocketValueRGBA *>(
socket->default_value);
copy_v3_v3(r_property.data(), socket_def_value->value);
break;
}
case SOCK_VECTOR: {
BLI_assert(r_property.size() == 3);
bNodeSocketValueVector *socket_def_value = static_cast<bNodeSocketValueVector *>(
socket->default_value);
copy_v3_v3(r_property.data(), socket_def_value->value);
break;
}
default: {
/* Other socket types are not handled here. */
BLI_assert(0);
break;
}
}
}
/**
* Collect all the source sockets linked to the destination socket in a destination node.
*/
static void linked_sockets_to_dest_id(const bNode *dest_node,
const nodes::NodeTreeRef &node_tree,
StringRefNull dest_socket_id,
Vector<const nodes::OutputSocketRef *> &r_linked_sockets)
{
r_linked_sockets.clear();
if (!dest_node) {
return;
}
Span<const nodes::NodeRef *> object_dest_nodes = node_tree.nodes_by_type(dest_node->idname);
Span<const nodes::InputSocketRef *> dest_inputs = object_dest_nodes.first()->inputs();
const nodes::InputSocketRef *dest_socket = nullptr;
for (const nodes::InputSocketRef *curr_socket : dest_inputs) {
if (STREQ(curr_socket->bsocket()->identifier, dest_socket_id.c_str())) {
dest_socket = curr_socket;
break;
}
}
if (dest_socket) {
Span<const nodes::OutputSocketRef *> linked_sockets = dest_socket->directly_linked_sockets();
r_linked_sockets.resize(linked_sockets.size());
r_linked_sockets = linked_sockets;
}
}
/**
* From a list of sockets, get the parent node which is of the given node type.
*/
static const bNode *get_node_of_type(Span<const nodes::OutputSocketRef *> sockets_list,
const int node_type)
{
for (const nodes::SocketRef *socket : sockets_list) {
const bNode *parent_node = socket->bnode();
if (parent_node->typeinfo->type == node_type) {
return parent_node;
}
}
return nullptr;
}
/**
* From a texture image shader node, get the image's filepath.
* Returned filepath is stripped of initial "//". If packed image is found,
* only the file "name" is returned.
*/
static const char *get_image_filepath(const bNode *tex_node)
{
if (!tex_node) {
return nullptr;
}
Image *tex_image = reinterpret_cast<Image *>(tex_node->id);
if (!tex_image || !BKE_image_has_filepath(tex_image)) {
return nullptr;
}
const char *path = tex_image->filepath;
if (BKE_image_has_packedfile(tex_image)) {
/* Put image in the same directory as the .MTL file. */
path = BLI_path_slash_rfind(path) + 1;
fprintf(stderr,
"Packed image found:'%s'. Unpack and place the image in the same "
"directory as the .MTL file.\n",
path);
}
if (path[0] == '/' && path[1] == '/') {
path += 2;
}
return path;
}
/**
* Find the Principled-BSDF Node in nodetree.
* We only want one that feeds directly into a Material Output node
* (that is the behavior of the legacy Python exporter).
*/
static const nodes::NodeRef *find_bsdf_node(const nodes::NodeTreeRef *nodetree)
{
if (!nodetree) {
return nullptr;
}
for (const nodes::NodeRef *node : nodetree->nodes_by_type("ShaderNodeOutputMaterial")) {
const nodes::InputSocketRef *node_input_socket0 = node->inputs()[0];
for (const nodes::OutputSocketRef *out_sock : node_input_socket0->directly_linked_sockets()) {
const nodes::NodeRef &in_node = out_sock->node();
if (in_node.typeinfo()->type == SH_NODE_BSDF_PRINCIPLED) {
return &in_node;
}
}
}
return nullptr;
}
/**
* Store properties found either in bNode or material into r_mtl_mat.
*/
static void store_bsdf_properties(const nodes::NodeRef *bsdf_node,
const Material *material,
MTLMaterial &r_mtl_mat)
{
const bNode *bnode = nullptr;
if (bsdf_node) {
bnode = bsdf_node->bnode();
}
/* If p-BSDF is not present, fallback to #Object.Material. */
float roughness = material->roughness;
if (bnode) {
copy_property_from_node(SOCK_FLOAT, bnode, "Roughness", {&roughness, 1});
}
/* Emperical approximation. Importer should use the inverse of this method. */
float spec_exponent = (1.0f - roughness) * 30;
spec_exponent *= spec_exponent;
float specular = material->spec;
if (bnode) {
copy_property_from_node(SOCK_FLOAT, bnode, "Specular", {&specular, 1});
}
float metallic = material->metallic;
if (bnode) {
copy_property_from_node(SOCK_FLOAT, bnode, "Metallic", {&metallic, 1});
}
float refraction_index = 1.0f;
if (bnode) {
copy_property_from_node(SOCK_FLOAT, bnode, "IOR", {&refraction_index, 1});
}
float dissolved = material->a;
if (bnode) {
copy_property_from_node(SOCK_FLOAT, bnode, "Alpha", {&dissolved, 1});
}
const bool transparent = dissolved != 1.0f;
float3 diffuse_col = {material->r, material->g, material->b};
if (bnode) {
copy_property_from_node(SOCK_RGBA, bnode, "Base Color", {diffuse_col, 3});
}
float3 emission_col{0.0f};
float emission_strength = 0.0f;
if (bnode) {
copy_property_from_node(SOCK_FLOAT, bnode, "Emission Strength", {&emission_strength, 1});
copy_property_from_node(SOCK_RGBA, bnode, "Emission", {emission_col, 3});
}
mul_v3_fl(emission_col, emission_strength);
/* See https://wikipedia.org/wiki/Wavefront_.obj_file for all possible values of illum. */
/* Highlight on. */
int illum = 2;
if (specular == 0.0f) {
/* Color on and Ambient on. */
illum = 1;
}
else if (metallic > 0.0f) {
/* Metallic ~= Reflection. */
if (transparent) {
/* Transparency: Refraction on, Reflection: ~~Fresnel off and Ray trace~~ on. */
illum = 6;
}
else {
/* Reflection on and Ray trace on. */
illum = 3;
}
}
else if (transparent) {
/* Transparency: Glass on, Reflection: Ray trace off */
illum = 9;
}
r_mtl_mat.Ns = spec_exponent;
if (metallic != 0.0f) {
r_mtl_mat.Ka = {metallic, metallic, metallic};
}
else {
r_mtl_mat.Ka = {1.0f, 1.0f, 1.0f};
}
r_mtl_mat.Kd = diffuse_col;
r_mtl_mat.Ks = {specular, specular, specular};
r_mtl_mat.Ke = emission_col;
r_mtl_mat.Ni = refraction_index;
r_mtl_mat.d = dissolved;
r_mtl_mat.illum = illum;
}
/**
* Store image texture options and filepaths in r_mtl_mat.
*/
static void store_image_textures(const nodes::NodeRef *bsdf_node,
const nodes::NodeTreeRef *node_tree,
const Material *material,
MTLMaterial &r_mtl_mat)
{
if (!material || !node_tree || !bsdf_node) {
/* No nodetree, no images, or no Principled BSDF node. */
return;
}
const bNode *bnode = bsdf_node->bnode();
/* Normal Map Texture has two extra tasks of:
* - finding a Normal Map node before finding a texture node.
* - finding "Strength" property of the node for `-bm` option.
*/
for (Map<const eMTLSyntaxElement, tex_map_XX>::MutableItem texture_map :
r_mtl_mat.texture_maps.items()) {
Vector<const nodes::OutputSocketRef *> linked_sockets;
const bNode *normal_map_node{nullptr};
if (texture_map.key == eMTLSyntaxElement::map_Bump) {
/* Find sockets linked to destination "Normal" socket in p-bsdf node. */
linked_sockets_to_dest_id(bnode, *node_tree, "Normal", linked_sockets);
/* Among the linked sockets, find Normal Map shader node. */
normal_map_node = get_node_of_type(linked_sockets, SH_NODE_NORMAL_MAP);
/* Find sockets linked to "Color" socket in normal map node. */
linked_sockets_to_dest_id(normal_map_node, *node_tree, "Color", linked_sockets);
}
else if (texture_map.key == eMTLSyntaxElement::map_Ke) {
float emission_strength = 0.0f;
copy_property_from_node(SOCK_FLOAT, bnode, "Emission Strength", {&emission_strength, 1});
if (emission_strength == 0.0f) {
continue;
}
}
else {
/* Find sockets linked to the destination socket of interest, in p-bsdf node. */
linked_sockets_to_dest_id(
bnode, *node_tree, texture_map.value.dest_socket_id, linked_sockets);
}
/* Among the linked sockets, find Image Texture shader node. */
const bNode *tex_node{get_node_of_type(linked_sockets, SH_NODE_TEX_IMAGE)};
if (!tex_node) {
continue;
}
const char *tex_image_filepath = get_image_filepath(tex_node);
if (!tex_image_filepath) {
continue;
}
/* Find "Mapping" node if connected to texture node. */
linked_sockets_to_dest_id(tex_node, *node_tree, "Vector", linked_sockets);
const bNode *mapping = get_node_of_type(linked_sockets, SH_NODE_MAPPING);
if (normal_map_node) {
copy_property_from_node(
SOCK_FLOAT, normal_map_node, "Strength", {&r_mtl_mat.map_Bump_strength, 1});
}
/* Texture transform options. Only translation (origin offset, "-o") and scale
* ("-o") are supported. */
copy_property_from_node(SOCK_VECTOR, mapping, "Location", {texture_map.value.translation, 3});
copy_property_from_node(SOCK_VECTOR, mapping, "Scale", {texture_map.value.scale, 3});
texture_map.value.image_path = tex_image_filepath;
}
}
MTLMaterial mtlmaterial_for_material(const Material *material)
{
BLI_assert(material != nullptr);
MTLMaterial mtlmat;
mtlmat.name = std::string(material->id.name + 2);
std::replace(mtlmat.name.begin(), mtlmat.name.end(), ' ', '_');
const nodes::NodeTreeRef *nodetree = nullptr;
if (material->nodetree) {
nodetree = new nodes::NodeTreeRef(material->nodetree);
}
const nodes::NodeRef *bsdf_node = find_bsdf_node(nodetree);
store_bsdf_properties(bsdf_node, material, mtlmat);
store_image_textures(bsdf_node, nodetree, material, mtlmat);
if (nodetree) {
delete nodetree;
}
return mtlmat;
}
} // namespace blender::io::obj

View File

@@ -0,0 +1,107 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup obj
*/
#pragma once
#include "BLI_float3.hh"
#include "BLI_map.hh"
#include "BLI_string_ref.hh"
#include "BLI_vector.hh"
#include "DNA_node_types.h"
#include "obj_export_io.hh"
namespace blender {
template<> struct DefaultHash<io::obj::eMTLSyntaxElement> {
uint64_t operator()(const io::obj::eMTLSyntaxElement value) const
{
return static_cast<uint64_t>(value);
}
};
} // namespace blender
namespace blender::io::obj {
class OBJMesh;
/**
* Generic container for texture node properties.
*/
struct tex_map_XX {
tex_map_XX(StringRef to_socket_id) : dest_socket_id(to_socket_id){};
/** Target socket which this texture node connects to. */
const std::string dest_socket_id;
float3 translation{0.0f};
float3 scale{1.0f};
/* Only Flat and Smooth projections are supported. */
int projection_type = SHD_PROJ_FLAT;
std::string image_path;
std::string mtl_dir_path;
};
/**
* Container suited for storing Material data for/from a .MTL file.
*/
struct MTLMaterial {
MTLMaterial()
{
texture_maps.add(eMTLSyntaxElement::map_Kd, tex_map_XX("Base Color"));
texture_maps.add(eMTLSyntaxElement::map_Ks, tex_map_XX("Specular"));
texture_maps.add(eMTLSyntaxElement::map_Ns, tex_map_XX("Roughness"));
texture_maps.add(eMTLSyntaxElement::map_d, tex_map_XX("Alpha"));
texture_maps.add(eMTLSyntaxElement::map_refl, tex_map_XX("Metallic"));
texture_maps.add(eMTLSyntaxElement::map_Ke, tex_map_XX("Emission"));
texture_maps.add(eMTLSyntaxElement::map_Bump, tex_map_XX("Normal"));
}
/**
* Caller must ensure that the given lookup key exists in the Map.
* \return Texture map corresponding to the given ID.
*/
tex_map_XX &tex_map_of_type(const eMTLSyntaxElement key)
{
{
BLI_assert(texture_maps.contains_as(key));
return texture_maps.lookup_as(key);
}
}
std::string name;
/* Always check for negative values while importing or exporting. Use defaults if
* any value is negative. */
float Ns{-1.0f};
float3 Ka{-1.0f};
float3 Kd{-1.0f};
float3 Ks{-1.0f};
float3 Ke{-1.0f};
float Ni{-1.0f};
float d{-1.0f};
int illum{-1};
Map<const eMTLSyntaxElement, tex_map_XX> texture_maps;
/** Only used for Normal Map node: "map_Bump". */
float map_Bump_strength{-1.0f};
};
MTLMaterial mtlmaterial_for_material(const Material *material);
} // namespace blender::io::obj

View File

@@ -0,0 +1,125 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup obj
*/
#include "BLI_float3.hh"
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
#include "IO_wavefront_obj.h"
#include "obj_export_nurbs.hh"
namespace blender::io::obj {
OBJCurve::OBJCurve(const Depsgraph *depsgraph,
const OBJExportParams &export_params,
Object *curve_object)
: export_object_eval_(curve_object)
{
export_object_eval_ = DEG_get_evaluated_object(depsgraph, curve_object);
export_curve_ = static_cast<Curve *>(export_object_eval_->data);
set_world_axes_transform(export_params.forward_axis, export_params.up_axis);
}
/**
* Set the final transform after applying axes settings and an Object's world transform.
*/
void OBJCurve::set_world_axes_transform(const eTransformAxisForward forward,
const eTransformAxisUp up)
{
float axes_transform[3][3];
unit_m3(axes_transform);
/* +Y-forward and +Z-up are the Blender's default axis settings. */
mat3_from_axis_conversion(OBJ_AXIS_Y_FORWARD, OBJ_AXIS_Z_UP, forward, up, axes_transform);
/* mat3_from_axis_conversion returns a transposed matrix! */
transpose_m3(axes_transform);
mul_m4_m3m4(world_axes_transform_, axes_transform, export_object_eval_->obmat);
/* #mul_m4_m3m4 does not transform last row of #Object.obmat, i.e. location data. */
mul_v3_m3v3(world_axes_transform_[3], axes_transform, export_object_eval_->obmat[3]);
world_axes_transform_[3][3] = export_object_eval_->obmat[3][3];
}
const char *OBJCurve::get_curve_name() const
{
return export_object_eval_->id.name + 2;
}
int OBJCurve::total_splines() const
{
return BLI_listbase_count(&export_curve_->nurb);
}
/**
* \param spline_index: Zero-based index of spline of interest.
* \return: Total vertices in a spline.
*/
int OBJCurve::total_spline_vertices(const int spline_index) const
{
const Nurb *const nurb = static_cast<Nurb *>(BLI_findlink(&export_curve_->nurb, spline_index));
return nurb->pntsu * nurb->pntsv;
}
/**
* Get coordinates of the vertex at the given index on the given spline.
*/
float3 OBJCurve::vertex_coordinates(const int spline_index,
const int vertex_index,
const float scaling_factor) const
{
const Nurb *const nurb = static_cast<Nurb *>(BLI_findlink(&export_curve_->nurb, spline_index));
float3 r_coord;
const BPoint &bpoint = nurb->bp[vertex_index];
copy_v3_v3(r_coord, bpoint.vec);
mul_m4_v3(world_axes_transform_, r_coord);
mul_v3_fl(r_coord, scaling_factor);
return r_coord;
}
/**
* Get total control points of the NURBS spline at the given index. This is different than total
* vertices of a spline.
*/
int OBJCurve::total_spline_control_points(const int spline_index) const
{
const Nurb *const nurb = static_cast<Nurb *>(BLI_findlink(&export_curve_->nurb, spline_index));
const int r_nurbs_degree = nurb->orderu - 1;
/* Total control points = Number of points in the curve (+ degree of the
* curve if it is cyclic). */
int r_tot_control_points = nurb->pntsv * nurb->pntsu;
if (nurb->flagu & CU_NURB_CYCLIC) {
r_tot_control_points += r_nurbs_degree;
}
return r_tot_control_points;
}
/**
* Get the degree of the NURBS spline at the given index.
*/
int OBJCurve::get_nurbs_degree(const int spline_index) const
{
const Nurb *const nurb = static_cast<Nurb *>(BLI_findlink(&export_curve_->nurb, spline_index));
return nurb->orderu - 1;
}
} // namespace blender::io::obj

View File

@@ -0,0 +1,60 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup obj
*/
#pragma once
#include "BLI_utility_mixins.hh"
#include "DNA_curve_types.h"
namespace blender::io::obj {
/**
* Provides access to the a Curve Object's properties.
* Only #CU_NURBS type is supported.
*
* \note Used for Curves to be exported in parameter form, and not converted to meshes.
*/
class OBJCurve : NonCopyable {
private:
const Object *export_object_eval_;
const Curve *export_curve_;
float world_axes_transform_[4][4];
public:
OBJCurve(const Depsgraph *depsgraph, const OBJExportParams &export_params, Object *curve_object);
const char *get_curve_name() const;
int total_splines() const;
int total_spline_vertices(const int spline_index) const;
float3 vertex_coordinates(const int spline_index,
const int vertex_index,
const float scaling_factor) const;
int total_spline_control_points(const int spline_index) const;
int get_nurbs_degree(const int spline_index) const;
private:
void set_world_axes_transform(const eTransformAxisForward forward, const eTransformAxisUp up);
};
} // namespace blender::io::obj

View File

@@ -0,0 +1,310 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup obj
*/
#include <cstdio>
#include <exception>
#include <memory>
#include "BKE_scene.h"
#include "BLI_path_util.h"
#include "BLI_vector.hh"
#include "DEG_depsgraph_query.h"
#include "DNA_scene_types.h"
#include "ED_object.h"
#include "obj_export_mesh.hh"
#include "obj_export_nurbs.hh"
#include "obj_exporter.hh"
#include "obj_export_file_writer.hh"
namespace blender::io::obj {
OBJDepsgraph::OBJDepsgraph(const bContext *C, const eEvaluationMode eval_mode)
{
Scene *scene = CTX_data_scene(C);
Main *bmain = CTX_data_main(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
if (eval_mode == DAG_EVAL_RENDER) {
depsgraph_ = DEG_graph_new(bmain, scene, view_layer, DAG_EVAL_RENDER);
needs_free_ = true;
DEG_graph_build_for_all_objects(depsgraph_);
BKE_scene_graph_evaluated_ensure(depsgraph_, bmain);
}
else {
depsgraph_ = CTX_data_ensure_evaluated_depsgraph(C);
needs_free_ = false;
}
}
OBJDepsgraph::~OBJDepsgraph()
{
if (needs_free_) {
DEG_graph_free(depsgraph_);
}
}
Depsgraph *OBJDepsgraph::get()
{
return depsgraph_;
}
void OBJDepsgraph::update_for_newframe()
{
BKE_scene_graph_update_for_newframe(depsgraph_);
}
static void print_exception_error(const std::system_error &ex)
{
std::cerr << ex.code().category().name() << ": " << ex.what() << ": " << ex.code().message()
<< std::endl;
}
/**
* Filter supported objects from the Scene.
*
* \note Curves are also stored with Meshes if export settings specify so.
*/
std::pair<Vector<std::unique_ptr<OBJMesh>>, Vector<std::unique_ptr<OBJCurve>>>
filter_supported_objects(Depsgraph *depsgraph, const OBJExportParams &export_params)
{
Vector<std::unique_ptr<OBJMesh>> r_exportable_meshes;
Vector<std::unique_ptr<OBJCurve>> r_exportable_nurbs;
const ViewLayer *view_layer = DEG_get_input_view_layer(depsgraph);
LISTBASE_FOREACH (const Base *, base, &view_layer->object_bases) {
Object *object_in_layer = base->object;
if (export_params.export_selected_objects && !(object_in_layer->base_flag & BASE_SELECTED)) {
continue;
}
switch (object_in_layer->type) {
case OB_SURF:
/* Export in mesh form: vertices and polygons. */
ATTR_FALLTHROUGH;
case OB_MESH: {
r_exportable_meshes.append(
std::make_unique<OBJMesh>(depsgraph, export_params, object_in_layer));
break;
}
case OB_CURVE: {
Curve *curve = static_cast<Curve *>(object_in_layer->data);
Nurb *nurb{static_cast<Nurb *>(curve->nurb.first)};
if (!nurb) {
/* An empty curve. Not yet supported to export these as meshes. */
if (export_params.export_curves_as_nurbs) {
r_exportable_nurbs.append(
std::make_unique<OBJCurve>(depsgraph, export_params, object_in_layer));
}
break;
}
switch (nurb->type) {
case CU_NURBS: {
if (export_params.export_curves_as_nurbs) {
/* Export in parameter form: control points. */
r_exportable_nurbs.append(
std::make_unique<OBJCurve>(depsgraph, export_params, object_in_layer));
}
else {
/* Export in mesh form: edges and vertices. */
r_exportable_meshes.append(
std::make_unique<OBJMesh>(depsgraph, export_params, object_in_layer));
}
break;
}
case CU_BEZIER: {
/* Always export in mesh form: edges and vertices. */
r_exportable_meshes.append(
std::make_unique<OBJMesh>(depsgraph, export_params, object_in_layer));
break;
}
default: {
/* Other curve types are not supported. */
break;
}
}
break;
}
default: {
/* Other object types are not supported. */
break;
}
}
}
return {std::move(r_exportable_meshes), std::move(r_exportable_nurbs)};
}
static void write_mesh_objects(Vector<std::unique_ptr<OBJMesh>> exportable_as_mesh,
OBJWriter &obj_writer,
MTLWriter *mtl_writer,
const OBJExportParams &export_params)
{
if (mtl_writer) {
obj_writer.write_mtllib_name(mtl_writer->mtl_file_path());
}
/* Smooth groups and UV vertex indices may make huge memory allocations, so they should be freed
* right after they're written, instead of waiting for #blender::Vector to clean them up after
* all the objects are exported. */
for (StealUniquePtr<OBJMesh> obj_mesh : exportable_as_mesh) {
obj_writer.write_object_name(*obj_mesh);
obj_writer.write_vertex_coords(*obj_mesh);
Vector<int> obj_mtlindices;
if (obj_mesh->tot_polygons() > 0) {
if (export_params.export_smooth_groups) {
obj_mesh->calc_smooth_groups(export_params.smooth_groups_bitflags);
}
if (export_params.export_normals) {
obj_writer.write_poly_normals(*obj_mesh);
}
if (export_params.export_uv) {
obj_writer.write_uv_coords(*obj_mesh);
}
if (mtl_writer) {
obj_mtlindices = mtl_writer->add_materials(*obj_mesh);
}
/* This function takes a 0-indexed slot index for the obj_mesh object and
* returns the material name that we are using in the .obj file for it. */
std::function<const char *(int)> matname_fn = [&](int s) -> const char * {
if (!mtl_writer || s < 0 || s >= obj_mtlindices.size()) {
return nullptr;
}
return mtl_writer->mtlmaterial_name(obj_mtlindices[s]);
};
obj_writer.write_poly_elements(*obj_mesh, matname_fn);
}
obj_writer.write_edges_indices(*obj_mesh);
obj_writer.update_index_offsets(*obj_mesh);
}
}
/**
* Export NURBS Curves in parameter form, not as vertices and edges.
*/
static void write_nurbs_curve_objects(const Vector<std::unique_ptr<OBJCurve>> &exportable_as_nurbs,
const OBJWriter &obj_writer)
{
/* #OBJCurve doesn't have any dynamically allocated memory, so it's fine
* to wait for #blender::Vector to clean the objects up. */
for (const std::unique_ptr<OBJCurve> &obj_curve : exportable_as_nurbs) {
obj_writer.write_nurbs_curve(*obj_curve);
}
}
/**
* Export a single frame to a .OBJ file.
*
* Conditionally write a .MTL file also.
*/
void export_frame(Depsgraph *depsgraph, const OBJExportParams &export_params, const char *filepath)
{
std::unique_ptr<OBJWriter> frame_writer = nullptr;
try {
frame_writer = std::make_unique<OBJWriter>(filepath, export_params);
}
catch (const std::system_error &ex) {
print_exception_error(ex);
return;
}
if (!frame_writer) {
BLI_assert(!"File should be writable by now.");
return;
}
std::unique_ptr<MTLWriter> mtl_writer = nullptr;
if (export_params.export_materials) {
try {
mtl_writer = std::make_unique<MTLWriter>(export_params.filepath);
}
catch (const std::system_error &ex) {
print_exception_error(ex);
}
}
frame_writer->write_header();
auto [exportable_as_mesh, exportable_as_nurbs] = filter_supported_objects(depsgraph,
export_params);
write_mesh_objects(
std::move(exportable_as_mesh), *frame_writer, mtl_writer.get(), export_params);
if (mtl_writer) {
mtl_writer->write_header(export_params.blen_filepath);
mtl_writer->write_materials();
}
write_nurbs_curve_objects(std::move(exportable_as_nurbs), *frame_writer);
}
/**
* Append the current frame number in the .OBJ file name.
*
* \return Whether the filepath is in #FILE_MAX limits.
*/
bool append_frame_to_filename(const char *filepath, const int frame, char *r_filepath_with_frames)
{
BLI_strncpy(r_filepath_with_frames, filepath, FILE_MAX);
BLI_path_extension_replace(r_filepath_with_frames, FILE_MAX, "");
const int digits = frame == 0 ? 1 : integer_digits_i(abs(frame));
BLI_path_frame(r_filepath_with_frames, frame, digits);
return BLI_path_extension_replace(r_filepath_with_frames, FILE_MAX, ".obj");
}
/**
* Central internal function to call Scene update & writer functions.
*/
void exporter_main(bContext *C, const OBJExportParams &export_params)
{
ED_object_mode_set(C, OB_MODE_OBJECT);
OBJDepsgraph obj_depsgraph(C, export_params.export_eval_mode);
Scene *scene = DEG_get_input_scene(obj_depsgraph.get());
const char *filepath = export_params.filepath;
/* Single frame export, i.e. no animation. */
if (!export_params.export_animation) {
fprintf(stderr, "Writing to %s\n", filepath);
export_frame(obj_depsgraph.get(), export_params, filepath);
return;
}
char filepath_with_frames[FILE_MAX];
/* Used to reset the Scene to its original state. */
const int original_frame = CFRA;
for (int frame = export_params.start_frame; frame <= export_params.end_frame; frame++) {
const bool filepath_ok = append_frame_to_filename(filepath, frame, filepath_with_frames);
if (!filepath_ok) {
fprintf(stderr, "Error: File Path too long.\n%s\n", filepath_with_frames);
return;
}
CFRA = frame;
obj_depsgraph.update_for_newframe();
fprintf(stderr, "Writing to %s\n", filepath_with_frames);
export_frame(obj_depsgraph.get(), export_params, filepath_with_frames);
}
CFRA = original_frame;
}
} // namespace blender::io::obj

View File

@@ -0,0 +1,82 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup obj
*/
#pragma once
#include "BLI_utility_mixins.hh"
#include "BLI_vector.hh"
#include "IO_wavefront_obj.h"
namespace blender::io::obj {
/**
* Steal elements' ownership in a range-based for-loop.
*/
template<typename T> struct StealUniquePtr {
std::unique_ptr<T> owning;
StealUniquePtr(std::unique_ptr<T> &owning) : owning(std::move(owning))
{
}
T *operator->()
{
return owning.operator->();
}
T &operator*()
{
return owning.operator*();
}
};
/**
* Behaves like `std::unique_ptr<Depsgraph, custom_deleter>`.
* Needed to free a new Depsgraph created for #DAG_EVAL_RENDER.
*/
class OBJDepsgraph : NonMovable, NonCopyable {
private:
Depsgraph *depsgraph_ = nullptr;
bool needs_free_ = false;
public:
OBJDepsgraph(const bContext *C, const eEvaluationMode eval_mode);
~OBJDepsgraph();
Depsgraph *get();
void update_for_newframe();
};
void exporter_main(bContext *C, const OBJExportParams &export_params);
class OBJMesh;
class OBJCurve;
void export_frame(Depsgraph *depsgraph,
const OBJExportParams &export_params,
const char *filepath);
std::pair<Vector<std::unique_ptr<OBJMesh>>, Vector<std::unique_ptr<OBJCurve>>>
filter_supported_objects(Depsgraph *depsgraph, const OBJExportParams &export_params);
bool append_frame_to_filename(const char *filepath, const int frame, char *r_filepath_with_frames);
} // namespace blender::io::obj

View File

@@ -0,0 +1,368 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup obj
*/
#include <array>
#include "BKE_displist.h"
#include "BKE_mesh.h"
#include "BLI_set.hh"
#include "DNA_object_types.h"
#include "IO_wavefront_obj.h"
#include "importer_mesh_utils.hh"
namespace blender::io::obj {
static float manhatten_len(const float3 coord)
{
return std::abs(coord[0]) + std::abs(coord[1]) + std::abs(coord[2]);
}
/**
* Keeps original index of the vertex as well as manhatten length for future use.
*/
struct vert_index_mlen {
const float3 v;
const int i;
const float mlen;
vert_index_mlen(float3 v, int i) : v(v), i(i), mlen(manhatten_len(v))
{
}
friend bool operator==(const vert_index_mlen &one, const vert_index_mlen &other)
{
return other.v == one.v;
}
friend bool operator!=(const vert_index_mlen &one, const vert_index_mlen &other)
{
return !(one == other);
}
};
/**
* Reorder vertices `v1` and `v2` so that edges like (v1,v2) and (v2,v2) are processed as the same.
*/
static std::pair<float3, float3> ed_key_mlen(const vert_index_mlen &v1, const vert_index_mlen &v2)
{
if (v2.mlen < v1.mlen) {
return {v2.v, v1.v};
}
return {v1.v, v2.v};
}
/**
* Join segments which have same starting or ending points.
* Caller should ensure non-empty segments.
*/
static bool join_segments(Vector<vert_index_mlen> *r_seg1, Vector<vert_index_mlen> *r_seg2)
{
if ((*r_seg1)[0].v == r_seg2->last().v) {
Vector<vert_index_mlen> *temp = r_seg1;
r_seg1 = r_seg2;
r_seg2 = temp;
}
else if (r_seg1->last().v == (*r_seg2)[0].v) {
}
else {
return false;
}
r_seg1->remove_last();
r_seg1->extend(*r_seg2);
if (r_seg1->last().v == (*r_seg1)[0].v) {
r_seg1->remove_last();
}
r_seg2->clear();
return true;
}
/**
* A simplified version of `M_Geometry_tessellate_polygon`.
*
* \param polyLineSeq List of polylines.
* \param r_new_line_seq Empty vector that fill be filled with indices of corners of triangles.
*/
static void tessellate_polygon(const Vector<Vector<float3>> &polyLineSeq,
Vector<Vector<int>> &r_new_line_seq)
{
int64_t totpoints = 0;
/* Display #ListBase. */
ListBase dispbase = {nullptr, nullptr};
const int64_t len_polylines{polyLineSeq.size()};
for (int i = 0; i < len_polylines; i++) {
Span<float3> polyLine = polyLineSeq[i];
const int64_t len_polypoints{polyLine.size()};
totpoints += len_polypoints;
if (len_polypoints <= 0) { /* don't bother adding edges as polylines */
continue;
}
DispList *dl = static_cast<DispList *>(MEM_callocN(sizeof(DispList), __func__));
BLI_addtail(&dispbase, dl);
dl->type = DL_INDEX3;
dl->nr = len_polypoints;
dl->type = DL_POLY;
dl->parts = 1; /* no faces, 1 edge loop */
dl->col = 0; /* no material */
dl->verts = static_cast<float *>(MEM_mallocN(sizeof(float[3]) * len_polypoints, "dl verts"));
dl->index = static_cast<int *>(MEM_callocN(sizeof(int[3]) * len_polypoints, "dl index"));
float *fp_verts = dl->verts;
for (int j = 0; j < len_polypoints; j++, fp_verts += 3) {
copy_v3_v3(fp_verts, polyLine[j]);
}
}
if (totpoints) {
/* now make the list to fill */
BKE_displist_fill(&dispbase, &dispbase, nullptr, false);
/* The faces are stored in a new DisplayList
* that's added to the head of the #ListBase. */
const DispList *dl = static_cast<DispList *>(dispbase.first);
for (int index = 0, *dl_face = dl->index; index < dl->parts; index++, dl_face += 3) {
r_new_line_seq.append({dl_face[0], dl_face[1], dl_face[2]});
}
BKE_displist_free(&dispbase);
}
}
/**
* Tessellate an ngon with holes to triangles.
*
* \param face_vertex_indices A polygon's indices that index into the given vertex coordinate list.
* \return List of polygons with each element containing indices of one polygon.
*/
Vector<Vector<int>> ngon_tessellate(Span<float3> vertex_coords, Span<int> face_vertex_indices)
{
if (face_vertex_indices.is_empty()) {
return {};
}
Vector<vert_index_mlen> verts;
verts.reserve(face_vertex_indices.size());
for (int i = 0; i < face_vertex_indices.size(); i++) {
verts.append({vertex_coords[face_vertex_indices[i]], i});
}
Vector<std::array<int, 2>> edges;
for (int i = 0; i < face_vertex_indices.size(); i++) {
edges.append({i, i - 1});
}
edges[0] = {0, static_cast<int>(face_vertex_indices.size() - 1)};
Set<std::pair<float3, float3>> edges_double;
{
Set<std::pair<float3, float3>> edges_used;
for (Span<int> edge : edges) {
std::pair<float3, float3> edge_key{ed_key_mlen(verts[edge[0]], verts[edge[1]])};
if (edges_used.contains(edge_key)) {
edges_double.add(edge_key);
}
else {
edges_used.add(edge_key);
}
}
}
Vector<Vector<vert_index_mlen>> loop_segments;
{
const vert_index_mlen *vert_prev = &verts[0];
Vector<vert_index_mlen> context_loop{1, *vert_prev};
loop_segments.append(context_loop);
for (const vert_index_mlen &vertex : verts) {
if (vertex == *vert_prev) {
continue;
}
if (edges_double.contains(ed_key_mlen(vertex, *vert_prev))) {
context_loop = {vertex};
loop_segments.append(context_loop);
}
else {
if (!context_loop.is_empty() && context_loop.last() == vertex) {
}
else {
loop_segments.last().append(vertex);
context_loop.append(vertex);
}
}
vert_prev = &vertex;
}
}
bool joining_segements = true;
while (joining_segements) {
joining_segements = false;
for (int j = loop_segments.size() - 1; j >= 0; j--) {
Vector<vert_index_mlen> &seg_j = loop_segments[j];
if (seg_j.is_empty()) {
continue;
}
for (int k = j - 1; k >= 0; k--) {
if (seg_j.is_empty()) {
break;
}
Vector<vert_index_mlen> &seg_k = loop_segments[k];
if (!seg_k.is_empty() && join_segments(&seg_j, &seg_k)) {
joining_segements = true;
}
}
}
}
for (Vector<vert_index_mlen> &loop : loop_segments) {
while (!loop.is_empty() && loop[0].v == loop.last().v) {
loop.remove_last();
}
}
Vector<Vector<vert_index_mlen>> loop_list;
for (Vector<vert_index_mlen> &loop : loop_segments) {
if (loop.size() > 2) {
loop_list.append(loop);
}
}
// Done with loop fixing.
Vector<int> vert_map(face_vertex_indices.size(), 0);
int ii = 0;
for (Span<vert_index_mlen> verts : loop_list) {
if (verts.size() <= 2) {
continue;
}
for (int i = 0; i < verts.size(); i++) {
vert_map[i + ii] = verts[i].i;
}
ii += verts.size();
}
Vector<Vector<int>> fill;
{
Vector<Vector<float3>> coord_list;
for (Span<vert_index_mlen> loop : loop_list) {
Vector<float3> coord;
for (const vert_index_mlen &vert : loop) {
coord.append(vert.v);
}
coord_list.append(coord);
}
tessellate_polygon(coord_list, fill);
}
Vector<Vector<int>> fill_indices;
Vector<Vector<int>> fill_indices_reversed;
for (Span<int> f : fill) {
Vector<int> tri;
for (const int i : f) {
tri.append(vert_map[i]);
}
fill_indices.append(tri);
}
if (fill_indices.is_empty()) {
std::cerr << "Warning: could not scanfill, fallback on triangle fan" << std::endl;
for (int i = 2; i < face_vertex_indices.size(); i++) {
fill_indices.append({0, i - 1, i});
}
}
else {
int flip = -1;
for (Span<int> fi : fill_indices) {
if (flip != -1) {
break;
}
for (int i = 0; i < fi.size(); i++) {
if (fi[i] == 0 && fi[i - 1] == 1) {
flip = 0;
break;
}
if (fi[i] == 1 && fi[i - 1] == 0) {
flip = 1;
break;
}
}
}
if (flip == 1) {
for (Span<int> fill_index : fill_indices) {
Vector<int> rev_face(fill_index.size());
for (int j = 0; j < rev_face.size(); j++) {
rev_face[j] = fill_index[rev_face.size() - 1 - j];
}
fill_indices_reversed.append(rev_face);
}
}
}
if (!fill_indices_reversed.is_empty()) {
return fill_indices_reversed;
}
return fill_indices;
}
/**
* Apply axes transform to the Object, and clamp object dimensions to the specified value.
*
* Ideally, this should be a member of a base class which `MeshFromGeometry` and
* `CurveFromGeometry` derive from.
*/
void transform_object(Object *object, const OBJImportParams &import_params)
{
float axes_transform[3][3];
unit_m3(axes_transform);
unit_m4(object->obmat);
/* Location shift should be 0. */
copy_v3_fl(object->obmat[3], 0.0f);
/* +Y-forward and +Z-up are the default Blender axis settings. */
mat3_from_axis_conversion(OBJ_AXIS_Y_FORWARD,
OBJ_AXIS_Z_UP,
import_params.forward_axis,
import_params.up_axis,
axes_transform);
/* mat3_from_axis_conversion returns a transposed matrix! */
transpose_m3(axes_transform);
mul_m4_m3m4(object->obmat, axes_transform, object->obmat);
if (import_params.clamp_size != 0.0f) {
float3 max_coord(-INT_MAX);
float3 min_coord(INT_MAX);
BoundBox *bb = BKE_mesh_boundbox_get(object);
for (const float(&vertex)[3] : bb->vec) {
for (int axis = 0; axis < 3; axis++) {
max_coord[axis] = max_ff(max_coord[axis], vertex[axis]);
min_coord[axis] = min_ff(min_coord[axis], vertex[axis]);
}
}
const float max_diff = max_fff(
max_coord[0] - min_coord[0], max_coord[1] - min_coord[1], max_coord[2] - min_coord[2]);
float scale = 1.0f;
while (import_params.clamp_size < max_diff * scale) {
scale = scale / 10;
}
copy_v3_fl(object->scale, scale);
}
}
} // namespace blender::io::obj

View File

@@ -0,0 +1,37 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup obj
*/
#pragma once
#include "BLI_float3.hh"
#include "BLI_span.hh"
#include "BLI_vector.hh"
struct Object;
struct OBJImportParams;
namespace blender::io::obj {
Vector<Vector<int>> ngon_tessellate(Span<float3> vertex_coords, Span<int> face_vertex_indices);
void transform_object(Object *object, const OBJImportParams &import_params);
} // namespace blender::io::obj

View File

@@ -0,0 +1,603 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup obj
*/
#include <fstream>
#include <iostream>
#include "BLI_map.hh"
#include "BLI_string_ref.hh"
#include "BLI_vector.hh"
#include "parser_string_utils.hh"
#include "obj_import_file_reader.hh"
namespace blender::io::obj {
using std::string;
/**
* Based on the properties of the given Geometry instance, create a new Geometry instance
* or return the previous one.
*
* Also update index offsets which should always happen if a new Geometry instance is created.
*/
static Geometry *create_geometry(Geometry *const prev_geometry,
const eGeometryType new_type,
StringRef name,
const GlobalVertices &global_vertices,
Vector<std::unique_ptr<Geometry>> &r_all_geometries,
VertexIndexOffset &r_offset)
{
auto new_geometry = [&]() {
if (name.is_empty()) {
r_all_geometries.append(std::make_unique<Geometry>(new_type, "New object"));
}
else {
r_all_geometries.append(std::make_unique<Geometry>(new_type, name));
}
r_offset.set_index_offset(global_vertices.vertices.size());
return r_all_geometries.last().get();
};
if (prev_geometry && prev_geometry->get_geom_type() == GEOM_MESH) {
/* After the creation of a Geometry instance, at least one element has been found in the OBJ
* file that indicates that it is a mesh. */
if (prev_geometry->total_verts() || prev_geometry->total_face_elems() ||
prev_geometry->total_normals() || prev_geometry->total_edges()) {
return new_geometry();
}
if (new_type == GEOM_MESH) {
/* A Geometry created initially with a default name now found its name. */
prev_geometry->set_geometry_name(name);
return prev_geometry;
}
if (new_type == GEOM_CURVE) {
/* The object originally created is not a mesh now that curve data
* follows the vertex coordinates list. */
prev_geometry->set_geom_type(GEOM_CURVE);
return prev_geometry;
}
}
if (prev_geometry && prev_geometry->get_geom_type() == GEOM_CURVE) {
return new_geometry();
}
return new_geometry();
}
void OBJStorer::add_vertex(const StringRef rest_line, GlobalVertices &r_global_vertices)
{
float3 curr_vert;
Vector<StringRef> str_vert_split;
split_by_char(rest_line, ' ', str_vert_split);
copy_string_to_float(str_vert_split, FLT_MAX, {curr_vert, 3});
r_global_vertices.vertices.append(curr_vert);
r_geom_.vertex_indices_.append(r_global_vertices.vertices.size() - 1);
}
void OBJStorer::add_vertex_normal(const StringRef rest_line, GlobalVertices &r_global_vertices)
{
float3 curr_vert_normal;
Vector<StringRef> str_vert_normal_split;
split_by_char(rest_line, ' ', str_vert_normal_split);
copy_string_to_float(str_vert_normal_split, FLT_MAX, {curr_vert_normal, 2});
r_global_vertices.vertex_normals.append(curr_vert_normal);
r_geom_.vertex_normal_indices_.append(r_global_vertices.vertex_normals.size() - 1);
}
void OBJStorer::add_uv_vertex(const StringRef rest_line, GlobalVertices &r_global_vertices)
{
float2 curr_uv_vert;
Vector<StringRef> str_uv_vert_split;
split_by_char(rest_line, ' ', str_uv_vert_split);
copy_string_to_float(str_uv_vert_split, FLT_MAX, {curr_uv_vert, 2});
r_global_vertices.uv_vertices.append(curr_uv_vert);
}
void OBJStorer::add_edge(const StringRef rest_line,
const VertexIndexOffset &offsets,
GlobalVertices &r_global_vertices)
{
int edge_v1 = -1, edge_v2 = -1;
Vector<StringRef> str_edge_split;
split_by_char(rest_line, ' ', str_edge_split);
copy_string_to_int(str_edge_split[0], -1, edge_v1);
copy_string_to_int(str_edge_split[1], -1, edge_v2);
/* Always keep stored indices non-negative and zero-based. */
edge_v1 += edge_v1 < 0 ? r_global_vertices.vertices.size() : -offsets.get_index_offset() - 1;
edge_v2 += edge_v2 < 0 ? r_global_vertices.vertices.size() : -offsets.get_index_offset() - 1;
BLI_assert(edge_v1 >= 0 && edge_v2 >= 0);
r_geom_.edges_.append({static_cast<uint>(edge_v1), static_cast<uint>(edge_v2)});
}
void OBJStorer::add_polygon(const StringRef rest_line,
const GlobalVertices &global_vertices,
const VertexIndexOffset &offsets,
const StringRef state_material_name,
const StringRef state_object_group,
const bool state_shaded_smooth)
{
PolyElem curr_face;
curr_face.shaded_smooth = state_shaded_smooth;
if (!state_material_name.is_empty()) {
curr_face.material_name = state_material_name;
}
if (!state_object_group.is_empty()) {
curr_face.vertex_group = state_object_group;
/* Yes it repeats several times, but another if-check will not reduce steps either. */
r_geom_.use_vertex_groups_ = true;
}
Vector<StringRef> str_corners_split;
split_by_char(rest_line, ' ', str_corners_split);
for (StringRef str_corner : str_corners_split) {
PolyCorner corner;
const size_t n_slash = std::count(str_corner.begin(), str_corner.end(), '/');
if (n_slash == 0) {
/* Case: "f v1 v2 v3". */
copy_string_to_int(str_corner, INT32_MAX, corner.vert_index);
}
else if (n_slash == 1) {
/* Case: "f v1/vt1 v2/vt2 v3/vt3". */
Vector<StringRef> vert_uv_split;
split_by_char(str_corner, '/', vert_uv_split);
copy_string_to_int(vert_uv_split[0], INT32_MAX, corner.vert_index);
if (vert_uv_split.size() == 2) {
copy_string_to_int(vert_uv_split[1], INT32_MAX, corner.uv_vert_index);
}
}
else if (n_slash == 2) {
/* Case: "f v1//vn1 v2//vn2 v3//vn3". */
/* Case: "f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3". */
Vector<StringRef> vert_uv_normal_split;
split_by_char(str_corner, '/', vert_uv_normal_split);
copy_string_to_int(vert_uv_normal_split[0], INT32_MAX, corner.vert_index);
copy_string_to_int(vert_uv_normal_split[1], INT32_MAX, corner.uv_vert_index);
if (vert_uv_normal_split.size() == 3) {
copy_string_to_int(vert_uv_normal_split[2], INT32_MAX, corner.vertex_normal_index);
}
}
/* Always keep stored indices non-negative and zero-based. */
corner.vert_index += corner.vert_index < 0 ? global_vertices.vertices.size() :
-offsets.get_index_offset() - 1;
corner.uv_vert_index += corner.uv_vert_index < 0 ? global_vertices.uv_vertices.size() : -1;
corner.vertex_normal_index += corner.vertex_normal_index < 0 ?
global_vertices.vertex_normals.size() :
-1;
curr_face.face_corners.append(corner);
}
r_geom_.face_elements_.append(curr_face);
r_geom_.total_loops_ += curr_face.face_corners.size();
}
void OBJStorer::set_curve_type(const StringRef rest_line,
const GlobalVertices &global_vertices,
const StringRef state_object_group,
VertexIndexOffset &r_offsets,
Vector<std::unique_ptr<Geometry>> &r_all_geometries)
{
if (rest_line.find("bspline") != StringRef::not_found) {
r_geom_ = *create_geometry(
&r_geom_, GEOM_CURVE, state_object_group, global_vertices, r_all_geometries, r_offsets);
r_geom_.nurbs_element_.group_ = state_object_group;
}
else {
std::cerr << "Curve type not supported:'" << rest_line << "'" << std::endl;
}
}
void OBJStorer::set_curve_degree(const StringRef rest_line)
{
copy_string_to_int(rest_line, 3, r_geom_.nurbs_element_.degree);
}
void OBJStorer::add_curve_vertex_indices(const StringRef rest_line,
const GlobalVertices &global_vertices)
{
Vector<StringRef> str_curv_split;
split_by_char(rest_line, ' ', str_curv_split);
/* Remove "0.0" and "1.0" from the strings. They are hardcoded. */
str_curv_split.remove(0);
str_curv_split.remove(0);
r_geom_.nurbs_element_.curv_indices.resize(str_curv_split.size());
copy_string_to_int(str_curv_split, INT32_MAX, r_geom_.nurbs_element_.curv_indices);
for (int &curv_index : r_geom_.nurbs_element_.curv_indices) {
/* Always keep stored indices non-negative and zero-based. */
curv_index += curv_index < 0 ? global_vertices.vertices.size() : -1;
}
}
void OBJStorer::add_curve_parameters(const StringRef rest_line)
{
Vector<StringRef> str_parm_split;
split_by_char(rest_line, ' ', str_parm_split);
if (str_parm_split[0] == "u" || str_parm_split[0] == "v") {
str_parm_split.remove(0);
r_geom_.nurbs_element_.parm.resize(str_parm_split.size());
copy_string_to_float(str_parm_split, FLT_MAX, r_geom_.nurbs_element_.parm);
}
else {
std::cerr << "Surfaces are not supported:'" << str_parm_split[0] << "'" << std::endl;
}
}
void OBJStorer::update_object_group(const StringRef rest_line,
std::string &r_state_object_group) const
{
if (rest_line.find("off") != string::npos || rest_line.find("null") != string::npos ||
rest_line.find("default") != string::npos) {
/* Set group for future elements like faces or curves to empty. */
r_state_object_group = "";
return;
}
r_state_object_group = rest_line;
}
void OBJStorer::update_polygon_material(const StringRef rest_line,
std::string &r_state_material_name) const
{
/* Materials may repeat if faces are written without sorting. */
r_geom_.material_names_.add(string(rest_line));
r_state_material_name = rest_line;
}
void OBJStorer::update_smooth_group(const StringRef rest_line, bool &r_state_shaded_smooth) const
{
/* Some implementations use "0" and "null" too, in addition to "off". */
if (rest_line != "0" && rest_line.find("off") == StringRef::not_found &&
rest_line.find("null") == StringRef::not_found) {
int smooth = 0;
copy_string_to_int(rest_line, 0, smooth);
r_state_shaded_smooth = smooth != 0;
}
else {
/* The OBJ file explicitly set shading to off. */
r_state_shaded_smooth = false;
}
}
/**
* Open OBJ file at the path given in import parameters.
*/
OBJParser::OBJParser(const OBJImportParams &import_params) : import_params_(import_params)
{
obj_file_.open(import_params_.filepath);
if (!obj_file_.good()) {
fprintf(stderr, "Cannot read from OBJ file:'%s'.\n", import_params_.filepath);
return;
}
fprintf(stderr, "Reading OBJ file from '%s'\n", import_params.filepath);
}
/**
* Read the OBJ file line by line and create OBJ Geometry instances. Also store all the vertex
* and UV vertex coordinates in a struct accessible by all objects.
*/
void OBJParser::parse(Vector<std::unique_ptr<Geometry>> &r_all_geometries,
GlobalVertices &r_global_vertices)
{
if (!obj_file_.good()) {
return;
}
string line;
/* Store vertex coordinates that belong to other Geometry instances. */
VertexIndexOffset offsets;
/* Non owning raw pointer to a Geometry. To be updated while creating a new Geometry. */
Geometry *current_geometry = create_geometry(
nullptr, GEOM_MESH, "", r_global_vertices, r_all_geometries, offsets);
/* State-setting variables: if set, they remain the same for the remaining
* elements in the object. */
bool state_shaded_smooth = false;
string state_object_group;
string state_material_name;
while (std::getline(obj_file_, line)) {
/* Keep reading new lines if the last character is `\`. */
/* Another way is to make a getline wrapper and use it in the while condition. */
read_next_line(obj_file_, line);
StringRef line_key, rest_line;
split_line_key_rest(line, line_key, rest_line);
if (line.empty() || rest_line.is_empty()) {
continue;
}
OBJStorer storer(*current_geometry);
switch (line_key_str_to_enum(line_key)) {
case eOBJLineKey::V: {
storer.add_vertex(rest_line, r_global_vertices);
break;
}
case eOBJLineKey::VN: {
storer.add_vertex_normal(rest_line, r_global_vertices);
break;
}
case eOBJLineKey::VT: {
storer.add_uv_vertex(rest_line, r_global_vertices);
break;
}
case eOBJLineKey::F: {
storer.add_polygon(rest_line,
r_global_vertices,
offsets,
state_material_name,
state_material_name,
state_shaded_smooth);
break;
}
case eOBJLineKey::L: {
storer.add_edge(rest_line, offsets, r_global_vertices);
break;
}
case eOBJLineKey::CSTYPE: {
storer.set_curve_type(
rest_line, r_global_vertices, state_object_group, offsets, r_all_geometries);
break;
}
case eOBJLineKey::DEG: {
storer.set_curve_degree(rest_line);
break;
}
case eOBJLineKey::CURV: {
storer.add_curve_vertex_indices(rest_line, r_global_vertices);
break;
}
case eOBJLineKey::PARM: {
storer.add_curve_parameters(rest_line);
break;
}
case eOBJLineKey::O: {
state_shaded_smooth = false;
state_object_group = "";
state_material_name = "";
current_geometry = create_geometry(
current_geometry, GEOM_MESH, rest_line, r_global_vertices, r_all_geometries, offsets);
break;
}
case eOBJLineKey::G: {
storer.update_object_group(rest_line, state_object_group);
break;
}
case eOBJLineKey::S: {
storer.update_smooth_group(rest_line, state_shaded_smooth);
break;
}
case eOBJLineKey::USEMTL: {
storer.update_polygon_material(rest_line, state_material_name);
break;
}
case eOBJLineKey::MTLLIB: {
mtl_libraries_.append(string(rest_line));
break;
}
case eOBJLineKey::COMMENT:
break;
default:
std::cout << "Element not recognised: '" << line_key << "'" << std::endl;
break;
}
}
}
/**
* Skip all texture map options and get the filepath from a "map_" line.
*/
static StringRef skip_unsupported_options(StringRef line)
{
TextureMapOptions map_options;
StringRef last_option;
int64_t last_option_pos = 0;
/* Find the last texture map option. */
for (StringRef option : map_options.all_options()) {
const int64_t pos{line.find(option)};
/* Equality (>=) takes care of finding an option in the beginning of the line. Avoid messing
* with signed-unsigned int comparison. */
if (pos != StringRef::not_found && pos >= last_option_pos) {
last_option = option;
last_option_pos = pos;
}
}
if (last_option.is_empty()) {
/* No option found, line is the filepath */
return line;
}
/* Remove upto start of the last option + size of the last option + space after it. */
line = line.drop_prefix(last_option_pos + last_option.size() + 1);
for (int i = 0; i < map_options.number_of_args(last_option); i++) {
const int64_t pos_space{line.find_first_of(' ')};
if (pos_space != StringRef::not_found) {
BLI_assert(pos_space + 1 < line.size());
line = line.drop_prefix(pos_space + 1);
}
}
return line;
}
/**
* Fix incoming texture map line keys for variations due to other exporters.
*/
static string fix_bad_map_keys(StringRef map_key)
{
string new_map_key(map_key);
if (map_key == "refl") {
new_map_key = "map_refl";
}
if (map_key.find("bump") != StringRef::not_found) {
/* Handles both "bump" and "map_Bump" */
new_map_key = "map_Bump";
}
return new_map_key;
}
/**
* Return a list of all material library filepaths referenced by the OBJ file.
*/
Span<std::string> OBJParser::mtl_libraries() const
{
return mtl_libraries_;
}
/**
* Open material library file.
*/
MTLParser::MTLParser(StringRef mtl_library, StringRefNull obj_filepath)
{
char obj_file_dir[FILE_MAXDIR];
BLI_split_dir_part(obj_filepath.data(), obj_file_dir, FILE_MAXDIR);
BLI_path_join(mtl_file_path_, FILE_MAX, obj_file_dir, mtl_library.data(), NULL);
BLI_split_dir_part(mtl_file_path_, mtl_dir_path_, FILE_MAXDIR);
mtl_file_.open(mtl_file_path_);
if (!mtl_file_.good()) {
fprintf(stderr, "Cannot read from MTL file:'%s'\n", mtl_file_path_);
return;
}
fprintf(stderr, "Reading MTL file from:'%s'\n", mtl_file_path_);
}
/**
* Read MTL file(s) and add MTLMaterial instances to the given Map reference.
*/
void MTLParser::parse_and_store(Map<string, std::unique_ptr<MTLMaterial>> &r_mtl_materials)
{
if (!mtl_file_.good()) {
return;
}
string line;
MTLMaterial *current_mtlmaterial = nullptr;
while (std::getline(mtl_file_, line)) {
StringRef line_key, rest_line;
split_line_key_rest(line, line_key, rest_line);
if (line.empty() || rest_line.is_empty()) {
continue;
}
/* Fix lower case/ incomplete texture map identifiers. */
const string fixed_key = fix_bad_map_keys(line_key);
line_key = fixed_key;
if (line_key == "newmtl") {
if (r_mtl_materials.remove_as(rest_line)) {
std::cerr << "Duplicate material found:'" << rest_line
<< "', using the last encountered Material definition." << std::endl;
}
current_mtlmaterial =
r_mtl_materials.lookup_or_add(string(rest_line), std::make_unique<MTLMaterial>()).get();
}
else if (line_key == "Ns") {
copy_string_to_float(rest_line, 324.0f, current_mtlmaterial->Ns);
}
else if (line_key == "Ka") {
Vector<StringRef> str_ka_split;
split_by_char(rest_line, ' ', str_ka_split);
copy_string_to_float(str_ka_split, 0.0f, {current_mtlmaterial->Ka, 3});
}
else if (line_key == "Kd") {
Vector<StringRef> str_kd_split;
split_by_char(rest_line, ' ', str_kd_split);
copy_string_to_float(str_kd_split, 0.8f, {current_mtlmaterial->Kd, 3});
}
else if (line_key == "Ks") {
Vector<StringRef> str_ks_split;
split_by_char(rest_line, ' ', str_ks_split);
copy_string_to_float(str_ks_split, 0.5f, {current_mtlmaterial->Ks, 3});
}
else if (line_key == "Ke") {
Vector<StringRef> str_ke_split;
split_by_char(rest_line, ' ', str_ke_split);
copy_string_to_float(str_ke_split, 0.0f, {current_mtlmaterial->Ke, 3});
}
else if (line_key == "Ni") {
copy_string_to_float(rest_line, 1.45f, current_mtlmaterial->Ni);
}
else if (line_key == "d") {
copy_string_to_float(rest_line, 1.0f, current_mtlmaterial->d);
}
else if (line_key == "illum") {
copy_string_to_int(rest_line, 2, current_mtlmaterial->illum);
}
/* Parse image textures. */
else if (line_key.find("map_") != StringRef::not_found) {
/* TODO howardt: fix this */
eMTLSyntaxElement line_key_enum = mtl_line_key_str_to_enum(line_key);
if (line_key_enum == eMTLSyntaxElement::string ||
!current_mtlmaterial->texture_maps.contains_as(line_key_enum)) {
/* No supported texture map found. */
std::cerr << "Texture map type not supported:'" << line_key << "'" << std::endl;
continue;
}
tex_map_XX &tex_map = current_mtlmaterial->texture_maps.lookup(line_key_enum);
Vector<StringRef> str_map_xx_split;
split_by_char(rest_line, ' ', str_map_xx_split);
/* TODO ankitm: use `skip_unsupported_options` for parsing these options too? */
const int64_t pos_o{str_map_xx_split.first_index_of_try("-o")};
if (pos_o != -1 && pos_o + 3 < str_map_xx_split.size()) {
copy_string_to_float({str_map_xx_split[pos_o + 1],
str_map_xx_split[pos_o + 2],
str_map_xx_split[pos_o + 3]},
0.0f,
{tex_map.translation, 3});
}
const int64_t pos_s{str_map_xx_split.first_index_of_try("-s")};
if (pos_s != -1 && pos_s + 3 < str_map_xx_split.size()) {
copy_string_to_float({str_map_xx_split[pos_s + 1],
str_map_xx_split[pos_s + 2],
str_map_xx_split[pos_s + 3]},
1.0f,
{tex_map.scale, 3});
}
/* Only specific to Normal Map node. */
const int64_t pos_bm{str_map_xx_split.first_index_of_try("-bm")};
if (pos_bm != -1 && pos_bm + 1 < str_map_xx_split.size()) {
copy_string_to_float(
str_map_xx_split[pos_bm + 1], 0.0f, current_mtlmaterial->map_Bump_strength);
}
const int64_t pos_projection{str_map_xx_split.first_index_of_try("-type")};
if (pos_projection != -1 && pos_projection + 1 < str_map_xx_split.size()) {
/* Only Sphere is supported, so whatever the type is, set it to Sphere. */
tex_map.projection_type = SHD_PROJ_SPHERE;
if (str_map_xx_split[pos_projection + 1] != "sphere") {
std::cerr << "Using projection type 'sphere', not:'"
<< str_map_xx_split[pos_projection + 1] << "'." << std::endl;
}
}
/* Skip all unsupported options and arguments. */
tex_map.image_path = string(skip_unsupported_options(rest_line));
tex_map.mtl_dir_path = mtl_dir_path_;
}
}
}
} // namespace blender::io::obj

View File

@@ -0,0 +1,203 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup obj
*/
#pragma once
#include <fstream>
#include "IO_wavefront_obj.h"
#include "obj_import_mtl.hh"
#include "obj_import_objects.hh"
namespace blender::io::obj {
class OBJParser {
private:
const OBJImportParams &import_params_;
std::ifstream obj_file_;
Vector<std::string> mtl_libraries_;
public:
OBJParser(const OBJImportParams &import_params);
void parse(Vector<std::unique_ptr<Geometry>> &r_all_geometries,
GlobalVertices &r_global_vertices);
Span<std::string> mtl_libraries() const;
};
class OBJStorer {
private:
Geometry &r_geom_;
public:
OBJStorer(Geometry &r_geom) : r_geom_(r_geom)
{
}
void add_vertex(const StringRef rest_line, GlobalVertices &r_global_vertices);
void add_vertex_normal(const StringRef rest_line, GlobalVertices &r_global_vertices);
void add_uv_vertex(const StringRef rest_line, GlobalVertices &r_global_vertices);
void add_edge(const StringRef rest_line,
const VertexIndexOffset &offsets,
GlobalVertices &r_global_vertices);
void add_polygon(const StringRef rest_line,
const GlobalVertices &global_vertices,
const VertexIndexOffset &offsets,
const StringRef state_material_name,
const StringRef state_object_group,
const bool state_shaded_smooth);
void set_curve_type(const StringRef rest_line,
const GlobalVertices &global_vertices,
const StringRef state_object_group,
VertexIndexOffset &r_offsets,
Vector<std::unique_ptr<Geometry>> &r_all_geometries);
void set_curve_degree(const StringRef rest_line);
void add_curve_vertex_indices(const StringRef rest_line, const GlobalVertices &global_vertices);
void add_curve_parameters(const StringRef rest_line);
void update_object_group(const StringRef rest_line, std::string &r_state_object_group) const;
void update_polygon_material(const StringRef rest_line,
std::string &r_state_material_name) const;
void update_smooth_group(const StringRef rest_line, bool &r_state_shaded_smooth) const;
};
enum class eOBJLineKey {
V,
VN,
VT,
F,
L,
CSTYPE,
DEG,
CURV,
PARM,
O,
G,
S,
USEMTL,
MTLLIB,
COMMENT
};
constexpr eOBJLineKey line_key_str_to_enum(const std::string_view key_str)
{
if (key_str == "v" || key_str == "V") {
return eOBJLineKey::V;
}
if (key_str == "vn" || key_str == "VN") {
return eOBJLineKey::VN;
}
if (key_str == "vt" || key_str == "VT") {
return eOBJLineKey::VT;
}
if (key_str == "f" || key_str == "F") {
return eOBJLineKey::F;
}
if (key_str == "l" || key_str == "L") {
return eOBJLineKey::L;
}
if (key_str == "cstype" || key_str == "CSTYPE") {
return eOBJLineKey::CSTYPE;
}
if (key_str == "deg" || key_str == "DEG") {
return eOBJLineKey::DEG;
}
if (key_str == "curv" || key_str == "CURV") {
return eOBJLineKey::CURV;
}
if (key_str == "parm" || key_str == "PARM") {
return eOBJLineKey::PARM;
}
if (key_str == "o" || key_str == "O") {
return eOBJLineKey::O;
}
if (key_str == "g" || key_str == "G") {
return eOBJLineKey::G;
}
if (key_str == "s" || key_str == "S") {
return eOBJLineKey::S;
}
if (key_str == "usemtl" || key_str == "USEMTL") {
return eOBJLineKey::USEMTL;
}
if (key_str == "mtllib" || key_str == "MTLLIB") {
return eOBJLineKey::MTLLIB;
}
if (key_str == "#") {
return eOBJLineKey::COMMENT;
}
return eOBJLineKey::COMMENT;
}
/**
* All texture map options with number of arguments they accept.
*/
class TextureMapOptions {
private:
Map<const std::string, int> tex_map_options;
public:
TextureMapOptions()
{
tex_map_options.add_new("-blendu", 1);
tex_map_options.add_new("-blendv", 1);
tex_map_options.add_new("-boost", 1);
tex_map_options.add_new("-mm", 2);
tex_map_options.add_new("-o", 3);
tex_map_options.add_new("-s", 3);
tex_map_options.add_new("-t", 3);
tex_map_options.add_new("-texres", 1);
tex_map_options.add_new("-clamp", 1);
tex_map_options.add_new("-bm", 1);
tex_map_options.add_new("-imfchan", 1);
}
/**
* All valid option strings.
*/
Map<const std::string, int>::KeyIterator all_options() const
{
return tex_map_options.keys();
}
int number_of_args(StringRef option) const
{
return tex_map_options.lookup_as(std::string(option));
}
};
class MTLParser {
private:
char mtl_file_path_[FILE_MAX];
/**
* Directory in which the MTL file is found.
*/
char mtl_dir_path_[FILE_MAX];
std::ifstream mtl_file_;
public:
MTLParser(StringRef mtl_library_, StringRefNull obj_filepath);
void parse_and_store(Map<std::string, std::unique_ptr<MTLMaterial>> &r_mtl_materials);
};
} // namespace blender::io::obj

View File

@@ -0,0 +1,413 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup obj
*/
#include <array>
#include "DNA_scene_types.h" /* For eVGroupSelect. */
#include "BKE_customdata.h"
#include "BKE_material.h"
#include "BKE_mesh.h"
#include "BKE_node.h"
#include "BKE_object.h"
#include "BKE_object_deform.h"
#include "BLI_map.hh"
#include "BLI_set.hh"
#include "BLI_vector_set.hh"
#include "bmesh.h"
#include "bmesh_operator_api.h"
#include "bmesh_tools.h"
#include "DNA_customdata_types.h"
#include "DNA_material_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_modifier_types.h"
#include "importer_mesh_utils.hh"
#include "obj_import_mesh.hh"
namespace blender::io::obj {
MeshFromGeometry::~MeshFromGeometry()
{
if (mesh_object_ || blender_mesh_) {
/* Move the object to own it. */
mesh_object_.reset();
blender_mesh_.reset();
BLI_assert(0);
}
}
void MeshFromGeometry::create_mesh(Main *bmain,
const Map<std::string, std::unique_ptr<MTLMaterial>> &materials,
const OBJImportParams &import_params)
{
std::string ob_name{mesh_geometry_.get_geometry_name()};
if (ob_name.empty()) {
ob_name = "Untitled";
}
Vector<PolyElem> new_faces;
Set<std::pair<int, int>> fgon_edges;
const auto [removed_faces, removed_loops]{tessellate_polygons(new_faces, fgon_edges)};
const int64_t tot_verts_object{mesh_geometry_.total_verts()};
/* Total explicitly imported edges, not the ones belonging the polygons to be created. */
const int64_t tot_edges{mesh_geometry_.total_edges()};
const int64_t tot_face_elems{mesh_geometry_.total_face_elems() - removed_faces +
new_faces.size()};
const int64_t tot_loops{mesh_geometry_.total_loops() - removed_loops + 3 * new_faces.size()};
blender_mesh_.reset(
BKE_mesh_new_nomain(tot_verts_object, tot_edges, 0, tot_loops, tot_face_elems));
mesh_object_.reset(BKE_object_add_only_object(bmain, OB_MESH, ob_name.c_str()));
mesh_object_->data = BKE_object_obdata_add_from_type(bmain, OB_MESH, ob_name.c_str());
create_vertices();
new_faces.extend(mesh_geometry_.face_elements());
create_polys_loops(new_faces);
create_edges();
create_uv_verts();
create_materials(bmain, materials);
bool verbose_validate = false;
#ifdef DEBUG
verbose_validate = true;
#endif
BKE_mesh_validate(blender_mesh_.get(), verbose_validate, false);
#if 0
/* TODO ankitm Check if it should be executed or not. */
add_custom_normals();
#endif
/* Un-tessellate unnecesarily triangulated n-gons. */
dissolve_edges(fgon_edges);
transform_object(mesh_object_.get(), import_params);
BKE_mesh_nomain_to_mesh(blender_mesh_.release(),
static_cast<Mesh *>(mesh_object_->data),
mesh_object_.get(),
&CD_MASK_EVERYTHING,
true);
}
/**
* Tessellate potentially invalid polygons and fill the
*/
std::pair<int64_t, int64_t> MeshFromGeometry::tessellate_polygons(
Vector<PolyElem> &r_new_faces, Set<std::pair<int, int>> &fgon_edges)
{
int64_t removed_faces = 0;
int64_t removed_loops = 0;
for (const PolyElem &curr_face : mesh_geometry_.face_elements()) {
if (curr_face.shaded_smooth || true) { // should be valid/invalid
return {removed_faces, removed_loops};
}
Vector<int> face_vert_indices;
Vector<int> face_uv_indices;
Vector<int> face_normal_indices;
face_vert_indices.reserve(curr_face.face_corners.size());
face_uv_indices.reserve(curr_face.face_corners.size());
face_normal_indices.reserve(curr_face.face_corners.size());
for (const PolyCorner &corner : curr_face.face_corners) {
face_vert_indices.append(corner.vert_index);
face_normal_indices.append(corner.vertex_normal_index);
face_uv_indices.append(corner.uv_vert_index);
removed_loops++;
}
Vector<Vector<int>> new_polygon_indices = ngon_tessellate(global_vertices_.vertices,
face_vert_indices);
for (Span<int> triangle : new_polygon_indices) {
r_new_faces.append({curr_face.vertex_group,
curr_face.material_name,
curr_face.shaded_smooth,
{{face_vert_indices[triangle[0]],
face_uv_indices[triangle[0]],
face_normal_indices[triangle[0]]},
{face_vert_indices[triangle[1]],
face_uv_indices[triangle[1]],
face_normal_indices[triangle[1]]},
{face_vert_indices[triangle[2]],
face_uv_indices[triangle[2]],
face_normal_indices[triangle[2]]}},
false});
}
if (new_polygon_indices.size() > 1) {
Set<std::pair<int, int>> edge_users;
for (Span<int> triangle : new_polygon_indices) {
int prev_vidx = face_vert_indices[triangle.last()];
for (const int ngidx : triangle) {
int vidx = face_vert_indices[ngidx];
if (vidx == prev_vidx) {
continue;
}
std::pair<int, int> edge_key = {min_ii(prev_vidx, vidx), max_ii(prev_vidx, vidx)};
prev_vidx = vidx;
if (edge_users.contains(edge_key)) {
fgon_edges.add(edge_key);
}
else {
edge_users.add(edge_key);
}
}
}
}
removed_faces++;
}
return {removed_faces, removed_loops};
}
void MeshFromGeometry::dissolve_edges(const Set<std::pair<int, int>> &fgon_edges)
{
if (fgon_edges.is_empty()) {
return;
}
const struct BMeshCreateParams bm_create_params = {1u};
/* If calc_face_normal is false, it triggers BLI_assert(BM_face_is_normal_valid(f)). */
const struct BMeshFromMeshParams bm_convert_params = {1u, 0, 0, 0};
BMesh *bmesh = BKE_mesh_to_bmesh_ex(blender_mesh_.get(), &bm_create_params, &bm_convert_params);
Vector<std::array<BMVert *, 2>> edges;
edges.reserve(fgon_edges.size());
BM_mesh_elem_table_ensure(bmesh, BM_VERT);
for (const std::pair<int, int> &edge : fgon_edges) {
edges.append({BM_vert_at_index(bmesh, edge.first), BM_vert_at_index(bmesh, edge.second)});
}
BMO_op_callf(bmesh,
BMO_FLAG_DEFAULTS,
"dissolve_edges edges=%eb use_verts=%b use_face_split=%b",
edges.data(),
0,
0);
unique_mesh_ptr to_free = std::move(blender_mesh_);
blender_mesh_.reset(BKE_mesh_from_bmesh_for_eval_nomain(bmesh, nullptr, to_free.get()));
to_free.reset();
BM_mesh_free(bmesh);
}
void MeshFromGeometry::create_vertices()
{
const int64_t tot_verts_object{mesh_geometry_.total_verts()};
for (int i = 0; i < tot_verts_object; ++i) {
if (mesh_geometry_.vertex_index(i) < global_vertices_.vertices.size()) {
copy_v3_v3(blender_mesh_->mvert[i].co,
global_vertices_.vertices[mesh_geometry_.vertex_index(i)]);
if (i >= mesh_geometry_.total_normals()) {
/* Silence debug warning in mesh validate. */
const float3 normals = {1.0f, 1.0f, 1.0f};
normal_float_to_short_v3(blender_mesh_->mvert[i].no, normals);
}
}
else {
std::cerr << "Vertex index:" << mesh_geometry_.vertex_index(i)
<< " larger than total vertices:" << global_vertices_.vertices.size() << " ."
<< std::endl;
}
}
}
/**
* Create polygons for the Mesh, set smooth shading flag, deform group name, assigned material
* also.
*
* It must receive all polygons to be added to the mesh. Remove holes from polygons before
* calling this.
*/
void MeshFromGeometry::create_polys_loops(Span<PolyElem> all_faces)
{
/* Will not be used if vertex groups are not imported. */
blender_mesh_->dvert = nullptr;
float weight = 0.0f;
if (mesh_geometry_.total_verts() && mesh_geometry_.use_vertex_groups()) {
blender_mesh_->dvert = static_cast<MDeformVert *>(CustomData_add_layer(
&blender_mesh_->vdata, CD_MDEFORMVERT, CD_CALLOC, nullptr, mesh_geometry_.total_verts()));
weight = 1.0f / mesh_geometry_.total_verts();
}
else {
UNUSED_VARS(weight);
}
/* Do not remove elements from the VectorSet since order of insertion is required.
* StringRef is fine since per-face deform group name outlives the VectorSet. */
VectorSet<StringRef> group_names;
const int64_t tot_face_elems{blender_mesh_->totpoly};
int tot_loop_idx = 0;
for (int poly_idx = 0; poly_idx < tot_face_elems; ++poly_idx) {
const PolyElem &curr_face = all_faces[poly_idx];
if (curr_face.face_corners.size() < 3) {
/* Don't add single vertex face, or edges. */
std::cerr << "Face with less than 3 vertices found, skipping." << std::endl;
continue;
}
MPoly &mpoly = blender_mesh_->mpoly[poly_idx];
mpoly.totloop = curr_face.face_corners.size();
mpoly.loopstart = tot_loop_idx;
if (curr_face.shaded_smooth) {
mpoly.flag |= ME_SMOOTH;
}
mpoly.mat_nr = mesh_geometry_.material_names().index_of_try(curr_face.material_name);
for (const PolyCorner &curr_corner : curr_face.face_corners) {
MLoop &mloop = blender_mesh_->mloop[tot_loop_idx];
tot_loop_idx++;
mloop.v = curr_corner.vert_index;
/* Set normals to silence mesh validate zero normals warnings. */
if (curr_corner.vertex_normal_index >= 0 &&
curr_corner.vertex_normal_index < global_vertices_.vertex_normals.size()) {
normal_float_to_short_v3(blender_mesh_->mvert[mloop.v].no,
global_vertices_.vertex_normals[curr_corner.vertex_normal_index]);
}
if (blender_mesh_->dvert) {
/* Iterating over mloop results in finding the same vertex multiple times.
* Another way is to allocate memory for dvert while creating vertices and fill them here.
*/
MDeformVert &def_vert = blender_mesh_->dvert[mloop.v];
if (!def_vert.dw) {
def_vert.dw = static_cast<MDeformWeight *>(
MEM_callocN(sizeof(MDeformWeight), "OBJ Import Deform Weight"));
}
/* Every vertex in a face is assigned the same deform group. */
int64_t pos_name{group_names.index_of_try(curr_face.vertex_group)};
if (pos_name == -1) {
group_names.add_new(curr_face.vertex_group);
pos_name = group_names.size() - 1;
}
BLI_assert(pos_name >= 0);
/* Deform group number (def_nr) must behave like an index into the names' list. */
*(def_vert.dw) = {static_cast<unsigned int>(pos_name), weight};
}
}
}
if (!blender_mesh_->dvert) {
return;
}
/* Add deform group(s) to the object's defbase. */
for (StringRef name : group_names) {
/* Adding groups in this order assumes that def_nr is an index into the names' list. */
BKE_object_defgroup_add_name(mesh_object_.get(), name.data());
}
}
/**
* Add explicitly imported OBJ edges to the mesh.
*/
void MeshFromGeometry::create_edges()
{
const int64_t tot_edges{mesh_geometry_.total_edges()};
for (int i = 0; i < tot_edges; ++i) {
const MEdge &src_edge = mesh_geometry_.edges()[i];
MEdge &dst_edge = blender_mesh_->medge[i];
BLI_assert(src_edge.v1 < mesh_geometry_.total_verts() &&
src_edge.v2 < mesh_geometry_.total_verts());
dst_edge.v1 = src_edge.v1;
dst_edge.v2 = src_edge.v2;
dst_edge.flag = ME_LOOSEEDGE;
}
/* Set argument `update` to true so that existing, explicitly imported edges can be merged
* with the new ones created from polygons. */
BKE_mesh_calc_edges(blender_mesh_.get(), true, false);
BKE_mesh_calc_edges_loose(blender_mesh_.get());
}
/**
* Add UV layer and vertices to the Mesh.
*/
void MeshFromGeometry::create_uv_verts()
{
if (global_vertices_.uv_vertices.size() <= 0) {
return;
}
MLoopUV *mluv_dst = static_cast<MLoopUV *>(CustomData_add_layer(
&blender_mesh_->ldata, CD_MLOOPUV, CD_DEFAULT, nullptr, mesh_geometry_.total_loops()));
int tot_loop_idx = 0;
for (const PolyElem &curr_face : mesh_geometry_.face_elements()) {
for (const PolyCorner &curr_corner : curr_face.face_corners) {
if (curr_corner.uv_vert_index >= 0 &&
curr_corner.uv_vert_index < global_vertices_.uv_vertices.size()) {
const float2 &mluv_src = global_vertices_.uv_vertices[curr_corner.uv_vert_index];
copy_v2_v2(mluv_dst[tot_loop_idx].uv, mluv_src);
tot_loop_idx++;
}
}
}
}
/**
* Add materials and the nodetree to the Mesh Object.
*/
void MeshFromGeometry::create_materials(
Main *bmain, const Map<std::string, std::unique_ptr<MTLMaterial>> &materials)
{
for (StringRef material_name : mesh_geometry_.material_names()) {
if (!materials.contains_as(material_name)) {
std::cerr << "Material named '" << material_name << "' not found in material library."
<< std::endl;
continue;
}
BKE_object_material_slot_add(bmain, mesh_object_.get());
Material *mat = BKE_material_add(bmain, material_name.data());
BKE_object_material_assign(
bmain, mesh_object_.get(), mat, mesh_object_->totcol, BKE_MAT_ASSIGN_USERPREF);
const MTLMaterial &curr_mat = *materials.lookup_as(material_name);
ShaderNodetreeWrap mat_wrap{bmain, curr_mat};
mat->use_nodes = true;
mat->nodetree = mat_wrap.get_nodetree();
ntreeUpdateTree(bmain, mat->nodetree);
}
}
/**
* Needs more clarity about what is expected in the viewport if the function works.
*/
void MeshFromGeometry::add_custom_normals()
{
const int64_t tot_loop_normals{mesh_geometry_.total_normals()};
float(*loop_normals)[3] = static_cast<float(*)[3]>(
MEM_malloc_arrayN(tot_loop_normals, sizeof(float[3]), __func__));
for (int index = 0; index < tot_loop_normals; index++) {
copy_v3_v3(loop_normals[index],
global_vertices_.vertex_normals[mesh_geometry_.vertex_normal_index(index)]);
}
blender_mesh_->flag |= ME_AUTOSMOOTH;
BKE_mesh_set_custom_normals(blender_mesh_.get(), loop_normals);
for (int i = 0; i < tot_loop_normals; i++) {
print_v3("", loop_normals[i]);
}
MEM_freeN(loop_normals);
}
} // namespace blender::io::obj

View File

@@ -0,0 +1,94 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup obj
*/
#pragma once
#include "BKE_lib_id.h"
#include "BLI_utility_mixins.hh"
#include "obj_import_mtl.hh"
#include "obj_import_objects.hh"
namespace blender::io::obj {
/**
* An custom unique_ptr deleter for a Mesh object.
*/
struct UniqueMeshDeleter {
void operator()(Mesh *mesh)
{
BKE_id_free(nullptr, mesh);
}
};
/**
* An unique_ptr to a Mesh with a custom deleter.
*/
using unique_mesh_ptr = std::unique_ptr<Mesh, UniqueMeshDeleter>;
/**
* Make a Blender Mesh Object from a Geometry of GEOM_MESH type.
* Use the mover function to own the mesh after creation.
*/
class MeshFromGeometry : NonMovable, NonCopyable {
private:
/**
* Mesh datablock made from OBJ data.
*/
unique_mesh_ptr blender_mesh_{nullptr};
/**
* An Object of type OB_MESH. Use the mover function to own it.
*/
unique_object_ptr mesh_object_{nullptr};
const Geometry &mesh_geometry_;
const GlobalVertices &global_vertices_;
public:
MeshFromGeometry(const Geometry &mesh_geometry, const GlobalVertices &global_vertices)
: mesh_geometry_(mesh_geometry), global_vertices_(global_vertices)
{
}
~MeshFromGeometry();
void create_mesh(Main *bmain,
const Map<std::string, std::unique_ptr<MTLMaterial>> &materials,
const OBJImportParams &import_params);
unique_object_ptr mover()
{
return std::move(mesh_object_);
}
private:
std::pair<int64_t, int64_t> tessellate_polygons(Vector<PolyElem> &new_faces,
Set<std::pair<int, int>> &fgon_edges);
void create_vertices();
void create_polys_loops(Span<PolyElem> all_faces);
void create_edges();
void create_uv_verts();
void create_materials(Main *bmain,
const Map<std::string, std::unique_ptr<MTLMaterial>> &materials);
void add_custom_normals();
void dissolve_edges(const Set<std::pair<int, int>> &fgon_edges);
};
} // namespace blender::io::obj

View File

@@ -0,0 +1,377 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup obj
*/
#include "BKE_image.h"
#include "BKE_node.h"
#include "BLI_map.hh"
#include "DNA_node_types.h"
#include "NOD_shader.h"
/* TODO: move eMTLSyntaxElement out of following file into a more neutral place */
#include "obj_export_io.hh"
#include "obj_import_mtl.hh"
#include "parser_string_utils.hh"
namespace blender::io::obj {
/**
* Set the socket's (of given ID) value to the given number(s).
* Only float value(s) can be set using this method.
*/
static void set_property_of_socket(eNodeSocketDatatype property_type,
StringRef socket_id,
Span<float> value,
bNode *r_node)
{
BLI_assert(r_node);
bNodeSocket *socket{nodeFindSocket(r_node, SOCK_IN, socket_id.data())};
BLI_assert(socket && socket->type == property_type);
switch (property_type) {
case SOCK_FLOAT: {
BLI_assert(value.size() == 1);
static_cast<bNodeSocketValueFloat *>(socket->default_value)->value = value[0];
break;
}
case SOCK_RGBA: {
/* Alpha will be added manually. It is not read from the MTL file either. */
BLI_assert(value.size() == 3);
copy_v3_v3(static_cast<bNodeSocketValueRGBA *>(socket->default_value)->value, value.data());
static_cast<bNodeSocketValueRGBA *>(socket->default_value)->value[3] = 1.0f;
break;
}
case SOCK_VECTOR: {
BLI_assert(value.size() == 3);
copy_v4_v4(static_cast<bNodeSocketValueVector *>(socket->default_value)->value,
value.data());
break;
}
default: {
BLI_assert(0);
break;
}
}
}
/**
* Load image for Image Texture node and set the node properties.
* Return success if Image can be loaded successfully.
*/
static bool load_texture_image(Main *bmain, const tex_map_XX &tex_map, bNode *r_node)
{
BLI_assert(r_node && r_node->type == SH_NODE_TEX_IMAGE);
std::string tex_file_path{tex_map.mtl_dir_path + tex_map.image_path};
Image *tex_image = BKE_image_load(bmain, tex_file_path.c_str());
if (!tex_image) {
/* Could be absolute, so load the image directly. */
fprintf(stderr, "Cannot load image file:'%s'\n", tex_file_path.c_str());
tex_image = BKE_image_load(bmain, tex_map.image_path.c_str());
}
if (!tex_image) {
fprintf(stderr, "Cannot load image file:'%s'\n", tex_map.image_path.c_str());
/* Remove quotes from the filepath. */
std::string no_quote_path{tex_map.mtl_dir_path +
replace_all_occurences(tex_map.image_path, "\"", "")};
tex_image = BKE_image_load(nullptr, no_quote_path.c_str());
if (!tex_image) {
fprintf(stderr, "Cannot load image file:'%s'\n", no_quote_path.data());
std::string no_underscore_path{replace_all_occurences(no_quote_path, "_", " ")};
tex_image = BKE_image_load(nullptr, no_underscore_path.c_str());
if (!tex_image) {
fprintf(stderr, "Cannot load image file:'%s'\n", no_underscore_path.data());
}
}
}
BLI_assert(tex_image);
if (tex_image) {
fprintf(stderr, "Loaded image from:'%s'\n", tex_image->filepath);
r_node->id = reinterpret_cast<ID *>(tex_image);
NodeTexImage *image = static_cast<NodeTexImage *>(r_node->storage);
image->projection = tex_map.projection_type;
return true;
}
return false;
}
/**
* Initializes a nodetree with a p-BSDF node's BSDF socket connected to shader output node's
* surface socket.
*/
ShaderNodetreeWrap::ShaderNodetreeWrap(Main *bmain, const MTLMaterial &mtl_mat) : mtl_mat_(mtl_mat)
{
nodetree_.reset(ntreeAddTree(nullptr, "Shader Nodetree", ntreeType_Shader->idname));
bsdf_.reset(add_node_to_tree(SH_NODE_BSDF_PRINCIPLED));
shader_output_.reset(add_node_to_tree(SH_NODE_OUTPUT_MATERIAL));
set_bsdf_socket_values();
add_image_textures(bmain);
link_sockets(std::move(bsdf_), "BSDF", shader_output_.get(), "Surface", 4);
nodeSetActive(nodetree_.get(), shader_output_.get());
}
/**
* Assert if caller hasn't acquired nodetree.
*/
ShaderNodetreeWrap::~ShaderNodetreeWrap()
{
if (nodetree_) {
/* nodetree's ownership must be acquired by the caller. */
nodetree_.reset();
BLI_assert(0);
}
}
/**
* Release nodetree for materials to own it. nodetree has its unique deleter
* if destructor is not reached for some reason.
*/
bNodeTree *ShaderNodetreeWrap::get_nodetree()
{
/* If this function has been reached, we know that nodes and the nodetree
* can be added to the scene safely. */
static_cast<void>(shader_output_.release());
return nodetree_.release();
}
/**
* Add a new static node to the tree.
* No two nodes are linked here.
*/
bNode *ShaderNodetreeWrap::add_node_to_tree(const int node_type)
{
return nodeAddStaticNode(nullptr, nodetree_.get(), node_type);
}
/**
* Return x-y coordinates for a node where y is determined by other nodes present in
* the same vertical column.
*/
std::pair<float, float> ShaderNodetreeWrap::set_node_locations(const int pos_x)
{
int pos_y = 0;
bool found = false;
while (true) {
for (Span<int> location : node_locations) {
if (location[0] == pos_x && location[1] == pos_y) {
pos_y += 1;
found = true;
}
else {
found = false;
}
}
if (!found) {
node_locations.append({pos_x, pos_y});
return {pos_x * node_size_, pos_y * node_size_ * 2.0 / 3.0};
}
}
}
/**
* Link two nodes by the sockets of given IDs.
* Also releases the ownership of the "from" node for nodetree to free it.
* \param from_node_pos_x 0 to 4 value as per nodetree arrangement.
*/
void ShaderNodetreeWrap::link_sockets(unique_node_ptr from_node,
StringRef from_node_id,
bNode *to_node,
StringRef to_node_id,
const int from_node_pos_x)
{
std::tie(from_node->locx, from_node->locy) = set_node_locations(from_node_pos_x);
std::tie(to_node->locx, to_node->locy) = set_node_locations(from_node_pos_x + 1);
bNodeSocket *from_sock{nodeFindSocket(from_node.get(), SOCK_OUT, from_node_id.data())};
bNodeSocket *to_sock{nodeFindSocket(to_node, SOCK_IN, to_node_id.data())};
BLI_assert(from_sock && to_sock);
nodeAddLink(nodetree_.get(), from_node.get(), from_sock, to_node, to_sock);
static_cast<void>(from_node.release());
}
/**
* Set values of sockets in p-BSDF node of the nodetree.
*/
void ShaderNodetreeWrap::set_bsdf_socket_values()
{
const int illum = mtl_mat_.illum;
bool do_highlight = false;
bool do_tranparency = false;
bool do_reflection = false;
bool do_glass = false;
switch (illum) {
case 1: {
/* Base color on, ambient on. */
break;
}
case 2: {
/* Highlight on. */
do_highlight = true;
break;
}
case 3: {
/* Reflection on and Ray trace on. */
do_reflection = true;
break;
}
case 4: {
/* Transparency: Glass on, Reflection: Ray trace on. */
do_glass = true;
do_reflection = true;
do_tranparency = true;
break;
}
case 5: {
/* Reflection: Fresnel on and Ray trace on. */
do_reflection = true;
break;
}
case 6: {
/* Transparency: Refraction on, Reflection: Fresnel off and Ray trace on. */
do_reflection = true;
do_tranparency = true;
break;
}
case 7: {
/* Transparency: Refraction on, Reflection: Fresnel on and Ray trace on. */
do_reflection = true;
do_tranparency = true;
break;
}
case 8: {
/* Reflection on and Ray trace off. */
do_reflection = true;
break;
}
case 9: {
/* Transparency: Glass on, Reflection: Ray trace off. */
do_glass = true;
do_reflection = false;
do_tranparency = true;
break;
}
default: {
std::cerr << "Warning! illum value = " << illum
<< "is not supported by the Principled-BSDF shader." << std::endl;
break;
}
}
float specular = (mtl_mat_.Ks[0] + mtl_mat_.Ks[1] + mtl_mat_.Ks[2]) / 3;
float roughness = 1.0f - 1.0f / 30 * sqrt(std::max(0.0f, std::min(900.0f, mtl_mat_.Ns)));
float metallic = (mtl_mat_.Ka[0] + mtl_mat_.Ka[1] + mtl_mat_.Ka[2]) / 3;
float ior = mtl_mat_.Ni;
float alpha = mtl_mat_.d;
if (specular < 0.0f) {
specular = static_cast<float>(do_highlight);
}
if (mtl_mat_.Ns < 0.0f) {
roughness = static_cast<float>(!do_highlight);
}
if (metallic < 0.0f) {
if (do_reflection) {
metallic = 1.0f;
}
}
else {
metallic = 0.0f;
}
if (ior < 0) {
if (do_tranparency) {
ior = 1.0f;
}
if (do_glass) {
ior = 1.5f;
}
}
if (alpha < 0) {
if (do_tranparency) {
alpha = 1.0f;
}
}
float3 base_color = {std::max(0.0f, mtl_mat_.Kd[0]),
std::max(0.0f, mtl_mat_.Kd[1]),
std::max(0.0f, mtl_mat_.Kd[2])};
float3 emission_color = {std::max(0.0f, mtl_mat_.Ke[0]),
std::max(0.0f, mtl_mat_.Ke[1]),
std::max(0.0f, mtl_mat_.Ke[2])};
set_property_of_socket(SOCK_RGBA, "Base Color", {base_color, 3}, bsdf_.get());
set_property_of_socket(SOCK_RGBA, "Emission", {emission_color, 3}, bsdf_.get());
if (mtl_mat_.texture_maps.contains_as(eMTLSyntaxElement::map_Ke)) {
set_property_of_socket(SOCK_FLOAT, "Emission Strength", {1.0f}, bsdf_.get());
}
set_property_of_socket(SOCK_FLOAT, "Specular", {specular}, bsdf_.get());
set_property_of_socket(SOCK_FLOAT, "Roughness", {roughness}, bsdf_.get());
set_property_of_socket(SOCK_FLOAT, "Metallic", {metallic}, bsdf_.get());
set_property_of_socket(SOCK_FLOAT, "IOR", {ior}, bsdf_.get());
set_property_of_socket(SOCK_FLOAT, "Alpha", {alpha}, bsdf_.get());
}
/**
* Create image texture, vector and normal mapping nodes from MTL materials and link the
* nodes to p-BSDF node.
*/
void ShaderNodetreeWrap::add_image_textures(Main *bmain)
{
for (const Map<const eMTLSyntaxElement, tex_map_XX>::Item texture_map :
mtl_mat_.texture_maps.items()) {
if (texture_map.value.image_path.empty()) {
/* No Image texture node of this map type can be added to this material. */
continue;
}
unique_node_ptr image_texture{add_node_to_tree(SH_NODE_TEX_IMAGE)};
unique_node_ptr mapping{add_node_to_tree(SH_NODE_MAPPING)};
unique_node_ptr texture_coordinate(add_node_to_tree(SH_NODE_TEX_COORD));
unique_node_ptr normal_map = nullptr;
if (texture_map.key == eMTLSyntaxElement::map_Bump) {
normal_map.reset(add_node_to_tree(SH_NODE_NORMAL_MAP));
const float bump = std::max(0.0f, mtl_mat_.map_Bump_strength);
set_property_of_socket(SOCK_FLOAT, "Strength", {bump}, normal_map.get());
}
if (!load_texture_image(bmain, texture_map.value, image_texture.get())) {
/* Image could not be added, so don't link image texture, vector, normal map nodes. */
continue;
}
set_property_of_socket(
SOCK_VECTOR, "Location", {texture_map.value.translation, 3}, mapping.get());
set_property_of_socket(SOCK_VECTOR, "Scale", {texture_map.value.scale, 3}, mapping.get());
link_sockets(std::move(texture_coordinate), "UV", mapping.get(), "Vector", 0);
link_sockets(std::move(mapping), "Vector", image_texture.get(), "Vector", 1);
if (normal_map) {
link_sockets(std::move(image_texture), "Color", normal_map.get(), "Color", 2);
link_sockets(std::move(normal_map), "Normal", bsdf_.get(), "Normal", 3);
}
else {
link_sockets(
std::move(image_texture), "Color", bsdf_.get(), texture_map.value.dest_socket_id, 2);
}
}
}
} // namespace blender::io::obj

View File

@@ -0,0 +1,115 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup obj
*/
#pragma once
#include <array>
#include "BLI_float3.hh"
#include "BLI_map.hh"
#include "BLI_string_ref.hh"
#include "BLI_vector.hh"
#include "DNA_node_types.h"
#include "MEM_guardedalloc.h"
#include "obj_export_mtl.hh"
namespace blender::io::obj {
struct UniqueNodeDeleter {
void operator()(bNode *node)
{
MEM_freeN(node);
}
};
struct UniqueNodetreeDeleter {
void operator()(bNodeTree *node)
{
MEM_freeN(node);
}
};
using unique_node_ptr = std::unique_ptr<bNode, UniqueNodeDeleter>;
using unique_nodetree_ptr = std::unique_ptr<bNodeTree, UniqueNodetreeDeleter>;
class ShaderNodetreeWrap {
private:
/* Node arrangement:
* Texture Coordinates -> Mapping -> Image Texture -> (optional) Normal Map -> p-BSDF -> Material
* Output. */
unique_nodetree_ptr nodetree_;
unique_node_ptr bsdf_;
unique_node_ptr shader_output_;
const MTLMaterial &mtl_mat_;
/* List of all locations occupied by nodes. */
Vector<std::array<int, 2>> node_locations;
const float node_size_{300.f};
public:
ShaderNodetreeWrap(Main *bmain, const MTLMaterial &mtl_mat);
~ShaderNodetreeWrap();
bNodeTree *get_nodetree();
private:
bNode *add_node_to_tree(const int node_type);
std::pair<float, float> set_node_locations(const int pos_x);
void link_sockets(unique_node_ptr from_node,
StringRef from_node_id,
bNode *to_node,
StringRef to_node_id,
const int from_node_pos_x);
void set_bsdf_socket_values();
void add_image_textures(Main *bmain);
};
constexpr eMTLSyntaxElement mtl_line_key_str_to_enum(const std::string_view key_str)
{
if (key_str == "map_Kd") {
return eMTLSyntaxElement::map_Kd;
}
if (key_str == "map_Ks") {
return eMTLSyntaxElement::map_Ks;
}
if (key_str == "map_Ns") {
return eMTLSyntaxElement::map_Ns;
}
if (key_str == "map_d") {
return eMTLSyntaxElement::map_d;
}
if (key_str == "refl" || key_str == "map_refl") {
return eMTLSyntaxElement::map_refl;
}
if (key_str == "map_Ke") {
return eMTLSyntaxElement::map_Ke;
}
if (key_str == "map_Bump" || key_str == "bump") {
return eMTLSyntaxElement::map_Bump;
}
return eMTLSyntaxElement::string;
}
} // namespace blender::io::obj

View File

@@ -0,0 +1,123 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup obj
*/
#include "BKE_object.h"
#include "DNA_curve_types.h"
#include "importer_mesh_utils.hh"
#include "obj_import_nurbs.hh"
#include "obj_import_objects.hh"
namespace blender::io::obj {
CurveFromGeometry::~CurveFromGeometry()
{
if (curve_object_ || blender_curve_) {
/* Move the object to own it. */
curve_object_.reset();
blender_curve_.reset();
BLI_assert(0);
}
}
void CurveFromGeometry::create_curve(Main *bmain, const OBJImportParams &import_params)
{
std::string ob_name{curve_geometry_.get_geometry_name()};
if (ob_name.empty() && !curve_geometry_.group().empty()) {
ob_name = curve_geometry_.group();
}
else {
ob_name = "Untitled";
}
blender_curve_.reset(BKE_curve_add(bmain, ob_name.c_str(), OB_CURVE));
curve_object_.reset(BKE_object_add_only_object(bmain, OB_CURVE, ob_name.c_str()));
blender_curve_->flag = CU_3D;
blender_curve_->resolu = blender_curve_->resolv = 12;
/* Only one NURBS spline will be created in the curve object. */
blender_curve_->actnu = 0;
Nurb *nurb = static_cast<Nurb *>(MEM_callocN(sizeof(Nurb), "OBJ import NURBS curve"));
BLI_addtail(BKE_curve_nurbs_get(blender_curve_.get()), nurb);
create_nurbs(import_params);
curve_object_->data = blender_curve_.release();
transform_object(curve_object_.get(), import_params);
}
/**
* Create a NURBS spline for the Curve converted from Geometry.
*/
void CurveFromGeometry::create_nurbs(const OBJImportParams & /*import_params */)
{
const NurbsElement &nurbs_geometry = curve_geometry_.nurbs_elem();
Nurb *nurb = static_cast<Nurb *>(blender_curve_->nurb.first);
nurb->type = CU_NURBS;
nurb->flag = CU_3D;
nurb->next = nurb->prev = nullptr;
/* BKE_nurb_points_add later on will update pntsu. If this were set to total curv points,
* we get double the total points in viewport. */
nurb->pntsu = 0;
/* Total points = pntsu * pntsv. */
nurb->pntsv = 1;
nurb->orderu = nurb->orderv = (nurbs_geometry.degree + 1 > SHRT_MAX) ? 4 :
nurbs_geometry.degree + 1;
nurb->resolu = nurb->resolv = blender_curve_->resolu;
const int64_t tot_vert{curve_geometry_.nurbs_elem().curv_indices.size()};
BKE_nurb_points_add(nurb, tot_vert);
for (int i = 0; i < tot_vert; i++) {
BPoint &bpoint = nurb->bp[i];
copy_v3_v3(bpoint.vec, global_vertices_.vertices[nurbs_geometry.curv_indices[i]]);
bpoint.vec[3] = 1.0f;
bpoint.weight = 1.0f;
}
BKE_nurb_knot_calc_u(nurb);
bool do_endpoints = true;
if (nurbs_geometry.curv_indices.size() &&
nurbs_geometry.parm.size() > nurbs_geometry.degree + 1) {
for (int i = 0; i < nurbs_geometry.degree + 1; i++) {
if (abs(nurbs_geometry.parm[i] - nurbs_geometry.curv_indices[0]) > 0.0001) {
do_endpoints = false;
break;
}
if (abs(nurbs_geometry.parm[-(i + 1)] - nurbs_geometry.curv_indices[1]) > 0.0001) {
do_endpoints = false;
break;
}
}
}
else {
do_endpoints = false;
}
if (do_endpoints) {
nurb->flagu = CU_NURB_ENDPOINT;
}
}
} // namespace blender::io::obj

View File

@@ -0,0 +1,86 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup obj
*/
#pragma once
#include <memory>
#include "BKE_curve.h"
#include "BLI_utility_mixins.hh"
#include "DNA_curve_types.h"
#include "obj_import_objects.hh"
namespace blender::io::obj {
/** Free a curve's memory using Blender's memory management. */
struct UniqueCurveDeleter {
void operator()(Curve *curve)
{
if (curve) {
BKE_nurbList_free(&curve->nurb);
}
}
};
/** An unique_ptr to a Curve with a custom deleter. Don't let unique_ptr free a curve with a
* different deallocator.
*/
using unique_curve_ptr = std::unique_ptr<Curve, UniqueCurveDeleter>;
/**
* Make a Blender NURBS Curve block from a Geometry of GEOM_CURVE type.
* Use the mover function to own the curve.
*/
class CurveFromGeometry : NonMovable, NonCopyable {
private:
/**
* Curve datablock of type CU_NURBS made from OBJ data..
*/
unique_curve_ptr blender_curve_;
/**
* Object of type OB_CURVE. Use the mover function to own it.
*/
unique_object_ptr curve_object_;
const Geometry &curve_geometry_;
const GlobalVertices &global_vertices_;
public:
CurveFromGeometry(const Geometry &geometry, const GlobalVertices &global_vertices)
: curve_geometry_(geometry), global_vertices_(global_vertices)
{
}
~CurveFromGeometry();
void create_curve(Main *bmain, const OBJImportParams &import_params);
unique_object_ptr mover()
{
return std::move(curve_object_);
}
private:
void create_nurbs(const OBJImportParams &import_params);
};
} // namespace blender::io::obj

View File

@@ -0,0 +1,155 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup obj
*/
#include "BKE_collection.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_build.h"
#include "DNA_collection_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "obj_import_objects.hh"
namespace blender::io::obj {
eGeometryType Geometry::get_geom_type() const
{
return geom_type_;
}
/**
* Use very rarely. Only when it is guaranteed that the
* type originally set is wrong.
*/
void Geometry::set_geom_type(const eGeometryType new_type)
{
geom_type_ = new_type;
}
StringRef Geometry::get_geometry_name() const
{
return geometry_name_;
}
void Geometry::set_geometry_name(StringRef new_name)
{
geometry_name_ = std::string(new_name);
}
/**
* Returns an index that ranges from zero to total coordinates in the
* global list of vertices.
*/
int64_t Geometry::vertex_index(const int64_t index) const
{
return vertex_indices_[index];
}
int64_t Geometry::total_verts() const
{
return vertex_indices_.size();
}
Span<PolyElem> Geometry::face_elements() const
{
return face_elements_;
}
const PolyElem &Geometry::ith_face_element(const int64_t index) const
{
return face_elements_[index];
}
int64_t Geometry::total_face_elems() const
{
return face_elements_.size();
}
bool Geometry::use_vertex_groups() const
{
return use_vertex_groups_;
}
Span<MEdge> Geometry::edges() const
{
return edges_;
}
int64_t Geometry::total_edges() const
{
return edges_.size();
}
int Geometry::total_loops() const
{
return total_loops_;
}
int64_t Geometry::vertex_normal_index(const int64_t vertex_index) const
{
return vertex_normal_indices_[vertex_index];
}
int64_t Geometry::total_normals() const
{
return vertex_normal_indices_.size();
}
const VectorSet<std::string> &Geometry::material_names() const
{
return material_names_;
}
const NurbsElement &Geometry::nurbs_elem() const
{
return nurbs_element_;
}
const std::string &Geometry::group() const
{
return nurbs_element_.group_;
}
/**
* Create a collection to store all imported objects.
*/
OBJImportCollection::OBJImportCollection(Main *bmain, Scene *scene) : bmain_(bmain), scene_(scene)
{
obj_import_collection_ = BKE_collection_add(
bmain_, scene_->master_collection, "OBJ import collection");
}
/**
* Add the given Mesh/Curve object to the OBJ import collection.
*/
void OBJImportCollection::add_object_to_collection(unique_object_ptr b_object)
{
BKE_collection_object_add(bmain_, obj_import_collection_, b_object.release());
id_fake_user_set(&obj_import_collection_->id);
DEG_id_tag_update(&obj_import_collection_->id, ID_RECALC_COPY_ON_WRITE);
DEG_relations_tag_update(bmain_);
}
} // namespace blender::io::obj

View File

@@ -0,0 +1,183 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup obj
*/
#pragma once
#include "BKE_lib_id.h"
#include "BLI_float2.hh"
#include "BLI_float3.hh"
#include "BLI_vector.hh"
#include "BLI_vector_set.hh"
#include "DNA_meshdata_types.h"
#include "DNA_object_types.h"
namespace blender::io::obj {
/**
* List of all vertex and UV vertex coordinates in an OBJ file accessible to any
* Geometry instance at any time.
*/
struct GlobalVertices {
Vector<float3> vertices;
Vector<float2> uv_vertices;
Vector<float3> vertex_normals;
};
/**
* Keeps track of the vertices that belong to other Geometries.
* Needed only for MLoop.v and MEdge.v1 which needs vertex indices ranging from (0 to total
* vertices in the mesh) as opposed to the other OBJ indices ranging from (0 to total vertices
* in the global list).
*/
struct VertexIndexOffset {
private:
int offset_ = 0;
public:
void set_index_offset(const int64_t total_vertices)
{
offset_ = total_vertices;
}
int64_t get_index_offset() const
{
return offset_;
}
};
/**
* A face's corner in an OBJ file. In Blender, it translates to a mloop vertex.
*/
struct PolyCorner {
/* These indices range from zero to total vertices in the OBJ file. */
int vert_index;
/* -1 is to indicate absence of UV vertices. Only < 0 condition should be checked since
* it can be less than -1 too. */
int uv_vert_index = -1;
int vertex_normal_index;
};
struct PolyElem {
std::string vertex_group;
std::string material_name;
bool shaded_smooth = false;
Vector<PolyCorner> face_corners;
/* Not read from the OBJ file. Set to true for potentially invalid polygons. */
bool invalid = false;
};
/**
* Contains data for one single NURBS curve in the OBJ file.
*/
struct NurbsElement {
/**
* For curves, groups may be used to specify multiple splines in the same curve object.
* It may also serve as the name of the curve if not specified explicitly.
*/
std::string group_;
int degree = 0;
/**
* Indices into the global list of vertex coordinates. Must be non-negative.
*/
Vector<int> curv_indices;
/* Values in the parm u/v line in a curve definition. */
Vector<float> parm;
};
enum eGeometryType {
GEOM_MESH = OB_MESH,
GEOM_CURVE = OB_CURVE,
};
class Geometry {
private:
eGeometryType geom_type_ = GEOM_MESH;
std::string geometry_name_;
VectorSet<std::string> material_names_;
/**
* Indices in the vector range from zero to total vertices in a geometry.
* Values range from zero to total coordinates in the global list.
*/
Vector<int> vertex_indices_;
Vector<int> vertex_normal_indices_;
/** Edges written in the file in addition to (or even without polygon) elements. */
Vector<MEdge> edges_;
Vector<PolyElem> face_elements_;
bool use_vertex_groups_ = false;
NurbsElement nurbs_element_;
int total_loops_ = 0;
public:
Geometry(eGeometryType type, StringRef ob_name)
: geom_type_(type), geometry_name_(std::string(ob_name)){};
eGeometryType get_geom_type() const;
void set_geom_type(const eGeometryType new_type);
StringRef get_geometry_name() const;
void set_geometry_name(StringRef new_name);
int64_t vertex_index(const int64_t index) const;
int64_t total_verts() const;
Span<PolyElem> face_elements() const;
const PolyElem &ith_face_element(const int64_t index) const;
int64_t total_face_elems() const;
bool use_vertex_groups() const;
Span<MEdge> edges() const;
int64_t total_edges() const;
int total_loops() const;
int64_t vertex_normal_index(const int64_t vertex_index) const;
int64_t total_normals() const;
const VectorSet<std::string> &material_names() const;
const NurbsElement &nurbs_elem() const;
const std::string &group() const;
friend class OBJStorer;
};
struct UniqueObjectDeleter {
void operator()(Object *object)
{
BKE_id_free(nullptr, object);
}
};
using unique_object_ptr = std::unique_ptr<Object, UniqueObjectDeleter>;
class OBJImportCollection {
private:
Main *bmain_;
Scene *scene_;
/**
* The collection that holds all the imported objects.
*/
Collection *obj_import_collection_;
public:
OBJImportCollection(Main *bmain, Scene *scene);
void add_object_to_collection(unique_object_ptr b_object);
};
} // namespace blender::io::obj

View File

@@ -0,0 +1,91 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup obj
*/
#include <string>
#include "BLI_float2.hh"
#include "BLI_float3.hh"
#include "BLI_map.hh"
#include "BLI_set.hh"
#include "BLI_string_ref.hh"
#include "BKE_scene.h"
#include "obj_import_file_reader.hh"
#include "obj_import_mesh.hh"
#include "obj_import_nurbs.hh"
#include "obj_import_objects.hh"
#include "obj_importer.hh"
namespace blender::io::obj {
/**
* Make Blender Mesh, Curve etc from Geometry and add them to the import collection.
*/
static void geometry_to_blender_objects(
Main *bmain,
Scene *scene,
const OBJImportParams &import_params,
Vector<std::unique_ptr<Geometry>> &all_geometries,
const GlobalVertices &global_vertices,
const Map<std::string, std::unique_ptr<MTLMaterial>> &materials)
{
OBJImportCollection import_collection{bmain, scene};
for (const std::unique_ptr<Geometry> &geometry : all_geometries) {
if (geometry->get_geom_type() == GEOM_MESH) {
MeshFromGeometry mesh_ob_from_geometry{*geometry, global_vertices};
mesh_ob_from_geometry.create_mesh(bmain, materials, import_params);
import_collection.add_object_to_collection(mesh_ob_from_geometry.mover());
}
else if (geometry->get_geom_type() == GEOM_CURVE) {
CurveFromGeometry curve_ob_from_geometry(*geometry, global_vertices);
curve_ob_from_geometry.create_curve(bmain, import_params);
import_collection.add_object_to_collection(curve_ob_from_geometry.mover());
}
}
}
void importer_main(bContext *C, const OBJImportParams &import_params)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
/* List of Geometry instances to be parsed from OBJ file. */
Vector<std::unique_ptr<Geometry>> all_geometries;
/* Container for vertex and UV vertex coordinates. */
GlobalVertices global_vertices;
/* List of MTLMaterial instances to be parsed from MTL file. */
Map<std::string, std::unique_ptr<MTLMaterial>> materials;
OBJParser obj_parser{import_params};
obj_parser.parse(all_geometries, global_vertices);
for (StringRef mtl_library : obj_parser.mtl_libraries()) {
MTLParser mtl_parser{mtl_library, import_params.filepath};
mtl_parser.parse_and_store(materials);
}
geometry_to_blender_objects(
bmain, scene, import_params, all_geometries, global_vertices, materials);
static_cast<void>(CTX_data_ensure_evaluated_depsgraph(C));
}
} // namespace blender::io::obj

View File

@@ -0,0 +1,31 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup obj
*/
#pragma once
#include "IO_wavefront_obj.h"
namespace blender::io::obj {
void importer_main(bContext *C, const OBJImportParams &import_params);
} // namespace blender::io::obj

View File

@@ -0,0 +1,217 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
#include <fstream>
#include <iostream>
#include <sstream>
#include "BLI_float3.hh"
#include "BLI_span.hh"
#include "BLI_string_ref.hh"
#include "BLI_vector.hh"
#include "parser_string_utils.hh"
namespace blender::io::obj {
using std::string;
/**
* Store multiple lines separated by an escaped newline character: `\\n`.
* Use this before doing any parse operations on the read string.
*/
void read_next_line(std::ifstream &file, string &r_line)
{
std::string new_line;
while (file.good() && !r_line.empty() && r_line.back() == '\\') {
new_line.clear();
const bool ok = static_cast<bool>(std::getline(file, new_line));
/* Remove the last backslash character. */
r_line.pop_back();
r_line.append(new_line);
if (!ok || new_line.empty()) {
return;
}
}
}
/**
* Split a line string into the first word (key) and the rest of the line.
* Also remove leading & trailing spaces as well as `\r` carriage return
* character if present.
*/
void split_line_key_rest(const StringRef line, StringRef &r_line_key, StringRef &r_rest_line)
{
if (line.is_empty()) {
return;
}
const int64_t pos_split{line.find_first_of(' ')};
if (pos_split == StringRef::not_found) {
/* Use the first character if no space is found in the line. It's usually a comment like:
* #This is a comment. */
r_line_key = line.substr(0, 1);
}
else {
r_line_key = line.substr(0, pos_split);
}
/* Eat the delimiter also using "+ 1". */
r_rest_line = line.drop_prefix(r_line_key.size() + 1);
if (r_rest_line.is_empty()) {
return;
}
/* Remove any leading spaces, trailing spaces & \r character, if any. */
const int64_t leading_space{r_rest_line.find_first_not_of(' ')};
if (leading_space != StringRef::not_found) {
r_rest_line = r_rest_line.drop_prefix(leading_space);
}
/* Another way is to do a test run before the actual parsing to find the newline
* character and use it in the getline. */
const int64_t carriage_return{r_rest_line.find_first_of('\r')};
if (carriage_return != StringRef::not_found) {
r_rest_line = r_rest_line.substr(0, carriage_return + 1);
}
const int64_t trailing_space{r_rest_line.find_last_not_of(' ')};
if (trailing_space != StringRef::not_found) {
/* The position is of a character that is not ' ', so count of characters is position + 1. */
r_rest_line = r_rest_line.substr(0, trailing_space + 1);
}
}
/**
* Split the given string by the delimiter and fill the given vector.
* If an intermediate string is empty, or space or null character, it is not appended to the
* vector.
*/
void split_by_char(StringRef in_string, const char delimiter, Vector<StringRef> &r_out_list)
{
r_out_list.clear();
while (!in_string.is_empty()) {
const int64_t pos_delim{in_string.find_first_of(delimiter)};
const int64_t word_len = pos_delim == StringRef::not_found ? in_string.size() : pos_delim;
StringRef word{in_string.data(), word_len};
if (!word.is_empty() && !(word == " " && !(word[0] == '\0'))) {
r_out_list.append(word);
}
if (pos_delim == StringRef::not_found) {
return;
}
/* Skip the word already stored. */
in_string = in_string.drop_prefix(word_len);
/* Skip all delimiters. */
in_string = in_string.drop_prefix(
std::min(in_string.find_first_not_of(delimiter), in_string.size()));
}
}
/**
* Convert the given string to float and assign it to the destination value.
*
* Catches exception if the string cannot be converted to a float. The destination value
* is set to the given fallback value in that case.
*/
void copy_string_to_float(StringRef src, const float fallback_value, float &r_dst)
{
try {
r_dst = std::stof(string(src));
}
catch (const std::invalid_argument &inv_arg) {
std::cerr << "Bad conversion to float:'" << inv_arg.what() << "':'" << src << "'" << std::endl;
r_dst = fallback_value;
}
catch (const std::out_of_range &out_of_range) {
std::cerr << "Out of range for float:'" << out_of_range.what() << ":'" << src << "'"
<< std::endl;
r_dst = fallback_value;
}
}
/**
* Convert all members of the Span of strings to floats and assign them to the float
* array members. Usually used for values like coordinates.
*
* Catches exception if any string cannot be converted to a float. The destination
* float is set to the given fallback value in that case.
*/
void copy_string_to_float(Span<StringRef> src,
const float fallback_value,
MutableSpan<float> r_dst)
{
BLI_assert(src.size() >= r_dst.size());
for (int i = 0; i < r_dst.size(); ++i) {
copy_string_to_float(src[i], fallback_value, r_dst[i]);
}
}
/**
* Convert the given string to int and assign it to the destination value.
*
* Catches exception if the string cannot be converted to an integer. The destination
* int is set to the given fallback value in that case.
*/
void copy_string_to_int(StringRef src, const int fallback_value, int &r_dst)
{
try {
r_dst = std::stoi(string(src));
}
catch (const std::invalid_argument &inv_arg) {
std::cerr << "Bad conversion to int:'" << inv_arg.what() << "':'" << src << "'" << std::endl;
r_dst = fallback_value;
}
catch (const std::out_of_range &out_of_range) {
std::cerr << "Out of range for int:'" << out_of_range.what() << ":'" << src << "'"
<< std::endl;
r_dst = fallback_value;
}
}
/**
* Convert the given strings to ints and fill the destination int buffer.
*
* Catches exception if any string cannot be converted to an integer. The destination
* int is set to the given fallback value in that case.
*/
void copy_string_to_int(Span<StringRef> src, const int fallback_value, MutableSpan<int> r_dst)
{
BLI_assert(src.size() >= r_dst.size());
for (int i = 0; i < r_dst.size(); ++i) {
copy_string_to_int(src[i], fallback_value, r_dst[i]);
}
}
std::string replace_all_occurences(StringRef original, StringRef to_remove, StringRef to_add)
{
std::string clean{original};
while (true) {
const std::string::size_type pos = clean.find(to_remove);
if (pos == std::string::npos) {
break;
}
clean.replace(pos, to_add.size(), to_add);
}
return clean;
}
} // namespace blender::io::obj

View File

@@ -0,0 +1,33 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
namespace blender::io::obj {
void read_next_line(std::ifstream &file, std::string &r_line);
void split_line_key_rest(StringRef line, StringRef &r_line_key, StringRef &r_rest_line);
void split_by_char(StringRef in_string, const char delimiter, Vector<StringRef> &r_out_list);
void copy_string_to_float(StringRef src, const float fallback_value, float &r_dst);
void copy_string_to_float(Span<StringRef> src,
const float fallback_value,
MutableSpan<float> r_dst);
void copy_string_to_int(StringRef src, const int fallback_value, int &r_dst);
void copy_string_to_int(Span<StringRef> src, const int fallback_value, MutableSpan<int> r_dst);
std::string replace_all_occurences(StringRef original, StringRef to_remove, StringRef to_add);
} // namespace blender::io::obj

View File

@@ -0,0 +1,416 @@
/* Apache License, Version 2.0 */
#include <fstream>
#include <gtest/gtest.h>
#include <ios>
#include <memory>
#include <sstream>
#include <string>
#include <system_error>
#include "testing/testing.h"
#include "tests/blendfile_loading_base_test.h"
#include "BKE_appdir.h"
#include "BKE_blender_version.h"
#include "BLI_fileops.h"
#include "BLI_index_range.hh"
#include "BLI_string_utf8.h"
#include "BLI_vector.hh"
#include "DEG_depsgraph.h"
#include "obj_export_file_writer.hh"
#include "obj_export_mesh.hh"
#include "obj_export_nurbs.hh"
#include "obj_exporter.hh"
#include "obj_exporter_tests.hh"
namespace blender::io::obj {
/* This is also the test name. */
class obj_exporter_test : public BlendfileLoadingBaseTest {
public:
/**
* \param filepath: relative to "tests" directory.
*/
bool load_file_and_depsgraph(const std::string &filepath,
const eEvaluationMode eval_mode = DAG_EVAL_VIEWPORT)
{
if (!blendfile_load(filepath.c_str())) {
return false;
}
depsgraph_create(eval_mode);
return true;
}
};
const std::string all_objects_file = "io_tests/blend_scene/all_objects.blend";
const std::string all_curve_objects_file = "io_tests/blend_scene/all_curves.blend";
TEST_F(obj_exporter_test, filter_objects_curves_as_mesh)
{
OBJExportParamsDefault _export;
if (!load_file_and_depsgraph(all_objects_file)) {
ADD_FAILURE();
return;
}
auto [objmeshes, objcurves]{filter_supported_objects(depsgraph, _export.params)};
EXPECT_EQ(objmeshes.size(), 17);
EXPECT_EQ(objcurves.size(), 0);
}
TEST_F(obj_exporter_test, filter_objects_curves_as_nurbs)
{
OBJExportParamsDefault _export;
if (!load_file_and_depsgraph(all_objects_file)) {
ADD_FAILURE();
return;
}
_export.params.export_curves_as_nurbs = true;
auto [objmeshes, objcurves]{filter_supported_objects(depsgraph, _export.params)};
EXPECT_EQ(objmeshes.size(), 16);
EXPECT_EQ(objcurves.size(), 2);
}
TEST_F(obj_exporter_test, filter_objects_selected)
{
OBJExportParamsDefault _export;
if (!load_file_and_depsgraph(all_objects_file)) {
ADD_FAILURE();
return;
}
_export.params.export_selected_objects = true;
_export.params.export_curves_as_nurbs = true;
auto [objmeshes, objcurves]{filter_supported_objects(depsgraph, _export.params)};
EXPECT_EQ(objmeshes.size(), 1);
EXPECT_EQ(objcurves.size(), 0);
}
TEST(obj_exporter_utils, append_negative_frame_to_filename)
{
const char path_original[FILE_MAX] = "/my_file.obj";
const char path_truth[FILE_MAX] = "/my_file-123.obj";
const int frame = -123;
char path_with_frame[FILE_MAX] = {0};
const bool ok = append_frame_to_filename(path_original, frame, path_with_frame);
EXPECT_TRUE(ok);
EXPECT_EQ_ARRAY(path_with_frame, path_truth, BLI_strlen_utf8(path_truth));
}
TEST(obj_exporter_utils, append_positive_frame_to_filename)
{
const char path_original[FILE_MAX] = "/my_file.obj";
const char path_truth[FILE_MAX] = "/my_file123.obj";
const int frame = 123;
char path_with_frame[FILE_MAX] = {0};
const bool ok = append_frame_to_filename(path_original, frame, path_with_frame);
EXPECT_TRUE(ok);
EXPECT_EQ_ARRAY(path_with_frame, path_truth, BLI_strlen_utf8(path_truth));
}
TEST_F(obj_exporter_test, curve_nurbs_points)
{
if (!load_file_and_depsgraph(all_curve_objects_file)) {
ADD_FAILURE();
return;
}
OBJExportParamsDefault _export;
_export.params.export_curves_as_nurbs = true;
auto [objmeshes_unused, objcurves]{filter_supported_objects(depsgraph, _export.params)};
for (StealUniquePtr<OBJCurve> objcurve : objcurves) {
if (all_nurbs_truth.count(objcurve->get_curve_name()) != 1) {
ADD_FAILURE();
return;
}
const NurbsObject *const nurbs_truth = all_nurbs_truth.at(objcurve->get_curve_name()).get();
EXPECT_EQ(objcurve->total_splines(), nurbs_truth->total_splines());
for (int spline_index : IndexRange(objcurve->total_splines())) {
EXPECT_EQ(objcurve->total_spline_vertices(spline_index),
nurbs_truth->total_spline_vertices(spline_index));
EXPECT_EQ(objcurve->get_nurbs_degree(spline_index),
nurbs_truth->get_nurbs_degree(spline_index));
EXPECT_EQ(objcurve->total_spline_control_points(spline_index),
nurbs_truth->total_spline_control_points(spline_index));
}
}
}
TEST_F(obj_exporter_test, curve_coordinates)
{
if (!load_file_and_depsgraph(all_curve_objects_file)) {
ADD_FAILURE();
return;
}
OBJExportParamsDefault _export;
_export.params.export_curves_as_nurbs = true;
auto [objmeshes_unused, objcurves]{filter_supported_objects(depsgraph, _export.params)};
for (StealUniquePtr<OBJCurve> objcurve : objcurves) {
if (all_nurbs_truth.count(objcurve->get_curve_name()) != 1) {
ADD_FAILURE();
return;
}
const NurbsObject *const nurbs_truth = all_nurbs_truth.at(objcurve->get_curve_name()).get();
EXPECT_EQ(objcurve->total_splines(), nurbs_truth->total_splines());
for (int spline_index : IndexRange(objcurve->total_splines())) {
for (int vertex_index : IndexRange(objcurve->total_spline_vertices(spline_index))) {
EXPECT_V3_NEAR(objcurve->vertex_coordinates(
spline_index, vertex_index, _export.params.scaling_factor),
nurbs_truth->vertex_coordinates(spline_index, vertex_index),
0.000001f);
}
}
}
}
static std::unique_ptr<OBJWriter> init_writer(const OBJExportParams &params,
const std::string out_filepath)
{
try {
auto writer = std::make_unique<OBJWriter>(out_filepath.c_str(), params);
return writer;
}
catch (const std::system_error &ex) {
std::cerr << ex.code().category().name() << ": " << ex.what() << ": " << ex.code().message()
<< std::endl;
return nullptr;
}
}
/* The following is relative to BKE_tempdir_base. */
const char *const temp_file_path = "output.OBJ";
static std::string read_temp_file_in_string(const std::string &file_path)
{
std::ifstream temp_stream(file_path);
std::ostringstream input_ss;
input_ss << temp_stream.rdbuf();
return input_ss.str();
}
TEST(obj_exporter_writer, header)
{
/* Because testing doesn't fully initialize Blender, we need the following. */
BKE_tempdir_init(NULL);
std::string out_file_path = blender::tests::flags_test_release_dir() + "/" + temp_file_path;
{
OBJExportParamsDefault _export;
std::unique_ptr<OBJWriter> writer = init_writer(_export.params, out_file_path);
if (!writer) {
ADD_FAILURE();
return;
}
writer->write_header();
}
const std::string result = read_temp_file_in_string(out_file_path);
using namespace std::string_literals;
ASSERT_EQ(result, "# Blender "s + BKE_blender_version_string() + "\n" + "# www.blender.org\n");
BLI_delete(out_file_path.c_str(), false, false);
}
TEST(obj_exporter_writer, mtllib)
{
std::string out_file_path = blender::tests::flags_test_release_dir() + "/" + temp_file_path;
{
OBJExportParamsDefault _export;
std::unique_ptr<OBJWriter> writer = init_writer(_export.params, out_file_path);
if (!writer) {
ADD_FAILURE();
return;
}
writer->write_mtllib_name("/Users/blah.mtl");
writer->write_mtllib_name("\\C:\\blah.mtl");
}
const std::string result = read_temp_file_in_string(out_file_path);
ASSERT_EQ(result, "mtllib blah.mtl\nmtllib blah.mtl\n");
}
/* Return true if string #a and string #b are equal after their first newline. */
static bool strings_equal_after_first_lines(const std::string &a, const std::string &b)
{
bool dbg_level = 0;
size_t a_len = a.size();
size_t b_len = b.size();
size_t a_next = a.find_first_of('\n');
size_t b_next = b.find_first_of('\n');
if (a_next == std::string::npos || b_next == std::string::npos) {
if (dbg_level > 0) {
std::cout << "Couldn't find newline in one of args\n";
}
return false;
}
if (dbg_level > 0) {
if (a.compare(a_next, a_len - a_next, b, b_next, b_len - b_next) != 0) {
for (int i = 0; i < a_len - a_next && i < b_len - b_next; ++i) {
if (a[a_next + i] != b[b_next + i]) {
std::cout << "Difference found at pos " << a_next + i << " of a\n";
std::cout << "a: " << a.substr(a_next + i, 100) << " ...\n";
std::cout << "b: " << b.substr(b_next + i, 100) << " ... \n";
return false;
}
}
}
else {
return true;
}
}
return a.compare(a_next, a_len - a_next, b, b_next, b_len - b_next) == 0;
}
/* From here on, tests are whole file tests, testing for golden output. */
class obj_exporter_regression_test : public obj_exporter_test {
public:
/**
* Export the given blend file with the given parameters and
* test to see if it matches a golden file (ignoring any difference in Blender version number).
* \param blendfile: input, relative to "tests" directory.
* \param golden_obj: expected output, relative to "tests" directory.
* \param params: the parameters to be used for export.
*/
void compare_obj_export_to_golden(const std::string &blendfile,
const std::string &golden_obj,
const std::string &golden_mtl,
OBJExportParams &params)
{
if (!load_file_and_depsgraph(blendfile)) {
return;
}
/* Because testing doesn't fully initialize Blender, we need the following. */
BKE_tempdir_init(NULL);
std::string tempdir = std::string(BKE_tempdir_base());
std::string out_file_path = tempdir + BLI_path_basename(golden_obj.c_str());
strncpy(params.filepath, out_file_path.c_str(), FILE_MAX);
params.blen_filepath = blendfile.c_str();
export_frame(depsgraph, params, out_file_path.c_str());
std::string output_str = read_temp_file_in_string(out_file_path);
std::string golden_file_path = blender::tests::flags_test_asset_dir() + "/" + golden_obj;
std::string golden_str = read_temp_file_in_string(golden_file_path);
ASSERT_TRUE(strings_equal_after_first_lines(output_str, golden_str));
BLI_delete(out_file_path.c_str(), false, false);
if (!golden_mtl.empty()) {
std::string out_mtl_file_path = tempdir + BLI_path_basename(golden_mtl.c_str());
std::string output_mtl_str = read_temp_file_in_string(out_mtl_file_path);
std::string golden_mtl_file_path = blender::tests::flags_test_asset_dir() + "/" + golden_mtl;
std::string golden_mtl_str = read_temp_file_in_string(golden_mtl_file_path);
ASSERT_TRUE(strings_equal_after_first_lines(output_mtl_str, golden_mtl_str));
BLI_delete(out_mtl_file_path.c_str(), false, false);
}
}
};
TEST_F(obj_exporter_regression_test, all_tris)
{
OBJExportParamsDefault _export;
compare_obj_export_to_golden("io_tests/blend_geometry/all_tris.blend",
"io_tests/obj/all_tris.obj",
"io_tests/obj/all_tris.mtl",
_export.params);
}
TEST_F(obj_exporter_regression_test, all_quads)
{
OBJExportParamsDefault _export;
_export.params.scaling_factor = 2.0f;
_export.params.export_materials = false;
compare_obj_export_to_golden(
"io_tests/blend_geometry/all_quads.blend", "io_tests/obj/all_quads.obj", "", _export.params);
}
TEST_F(obj_exporter_regression_test, fgons)
{
OBJExportParamsDefault _export;
_export.params.forward_axis = OBJ_AXIS_Y_FORWARD;
_export.params.up_axis = OBJ_AXIS_Z_UP;
_export.params.export_materials = false;
compare_obj_export_to_golden(
"io_tests/blend_geometry/fgons.blend", "io_tests/obj/fgons.obj", "", _export.params);
}
TEST_F(obj_exporter_regression_test, edges)
{
OBJExportParamsDefault _export;
_export.params.forward_axis = OBJ_AXIS_Y_FORWARD;
_export.params.up_axis = OBJ_AXIS_Z_UP;
_export.params.export_materials = false;
compare_obj_export_to_golden(
"io_tests/blend_geometry/edges.blend", "io_tests/obj/edges.obj", "", _export.params);
}
TEST_F(obj_exporter_regression_test, vertices)
{
OBJExportParamsDefault _export;
_export.params.forward_axis = OBJ_AXIS_Y_FORWARD;
_export.params.up_axis = OBJ_AXIS_Z_UP;
_export.params.export_materials = false;
compare_obj_export_to_golden(
"io_tests/blend_geometry/vertices.blend", "io_tests/obj/vertices.obj", "", _export.params);
}
TEST_F(obj_exporter_regression_test, nurbs_as_nurbs)
{
OBJExportParamsDefault _export;
_export.params.forward_axis = OBJ_AXIS_Y_FORWARD;
_export.params.up_axis = OBJ_AXIS_Z_UP;
_export.params.export_materials = false;
_export.params.export_curves_as_nurbs = true;
compare_obj_export_to_golden(
"io_tests/blend_geometry/nurbs.blend", "io_tests/obj/nurbs.obj", "", _export.params);
}
TEST_F(obj_exporter_regression_test, nurbs_as_mesh)
{
OBJExportParamsDefault _export;
_export.params.forward_axis = OBJ_AXIS_Y_FORWARD;
_export.params.up_axis = OBJ_AXIS_Z_UP;
_export.params.export_materials = false;
_export.params.export_curves_as_nurbs = false;
compare_obj_export_to_golden(
"io_tests/blend_geometry/nurbs.blend", "io_tests/obj/nurbs_mesh.obj", "", _export.params);
}
TEST_F(obj_exporter_regression_test, cube_all_data_triangulated)
{
OBJExportParamsDefault _export;
_export.params.forward_axis = OBJ_AXIS_Y_FORWARD;
_export.params.up_axis = OBJ_AXIS_Z_UP;
_export.params.export_materials = false;
_export.params.export_triangulated_mesh = true;
compare_obj_export_to_golden("io_tests/blend_geometry/cube_all_data.blend",
"io_tests/obj/cube_all_data_triangulated.obj",
"",
_export.params);
}
TEST_F(obj_exporter_regression_test, suzanne_all_data)
{
OBJExportParamsDefault _export;
_export.params.forward_axis = OBJ_AXIS_Y_FORWARD;
_export.params.up_axis = OBJ_AXIS_Z_UP;
_export.params.export_materials = false;
_export.params.export_smooth_groups = true;
compare_obj_export_to_golden("io_tests/blend_geometry/suzanne_all_data.blend",
"io_tests/obj/suzanne_all_data.obj",
"",
_export.params);
}
TEST_F(obj_exporter_regression_test, all_objects)
{
OBJExportParamsDefault _export;
_export.params.forward_axis = OBJ_AXIS_Y_FORWARD;
_export.params.up_axis = OBJ_AXIS_Z_UP;
_export.params.export_smooth_groups = true;
compare_obj_export_to_golden("io_tests/blend_scene/all_objects.blend",
"io_tests/obj/all_objects.obj",
"io_tests/obj/all_objects.mtl",
_export.params);
}
} // namespace blender::io::obj

View File

@@ -0,0 +1,149 @@
/* Apache License, Version 2.0 */
/**
* This file contains default values for several items like
* vertex coordinates, export parameters, MTL values etc.
*/
#pragma once
#include <array>
#include <gtest/gtest.h>
#include <string>
#include <vector>
#include "IO_wavefront_obj.h"
namespace blender::io::obj {
using array_float_3 = std::array<float, 3>;
/**
* This matches #OBJCurve's member functions, except that all the numbers and names are known
* constants. Used to store expected values of NURBS Curve sobjects.
*/
class NurbsObject {
private:
std::string nurbs_name_;
/* The indices in these vectors are spline indices. */
std::vector<std::vector<array_float_3>> coordinates_;
std::vector<int> degrees_;
std::vector<int> control_points_;
public:
NurbsObject(const std::string nurbs_name,
const std::vector<std::vector<array_float_3>> coordinates,
const std::vector<int> degrees,
const std::vector<int> control_points)
: nurbs_name_(nurbs_name),
coordinates_(coordinates),
degrees_(degrees),
control_points_(control_points)
{
}
int total_splines() const
{
return coordinates_.size();
}
int total_spline_vertices(const int spline_index) const
{
if (spline_index >= coordinates_.size()) {
ADD_FAILURE();
return 0;
}
return coordinates_[spline_index].size();
}
const float *vertex_coordinates(const int spline_index, const int vertex_index) const
{
return coordinates_[spline_index][vertex_index].data();
}
int get_nurbs_degree(const int spline_index) const
{
return degrees_[spline_index];
}
int total_spline_control_points(const int spline_index) const
{
return control_points_[spline_index];
}
};
struct OBJExportParamsDefault {
OBJExportParams params;
OBJExportParamsDefault()
{
params.filepath[0] = '\0';
params.blen_filepath = "";
params.export_animation = false;
params.start_frame = 0;
params.end_frame = 1;
params.forward_axis = OBJ_AXIS_NEGATIVE_Z_FORWARD;
params.up_axis = OBJ_AXIS_Y_UP;
params.scaling_factor = 1.f;
params.export_eval_mode = DAG_EVAL_VIEWPORT;
params.export_selected_objects = false;
params.export_uv = true;
params.export_normals = true;
params.export_materials = true;
params.export_triangulated_mesh = false;
params.export_curves_as_nurbs = false;
params.export_object_groups = false;
params.export_material_groups = false;
params.export_vertex_groups = false;
params.export_smooth_groups = true;
params.smooth_groups_bitflags = false;
}
};
const std::vector<std::vector<array_float_3>> coordinates_NurbsCurve{
{{6.94742, 0.000000, 0.000000},
{7.44742, 0.000000, -1.000000},
{9.44742, 0.000000, -1.000000},
{9.94742, 0.000000, 0.000000}}};
const std::vector<std::vector<array_float_3>> coordinates_NurbsCircle{
{{11.463165, 0.000000, 1.000000},
{10.463165, 0.000000, 1.000000},
{10.463165, 0.000000, 0.000000},
{10.463165, 0.000000, -1.000000},
{11.463165, 0.000000, -1.000000},
{12.463165, 0.000000, -1.000000},
{12.463165, 0.000000, 0.000000},
{12.463165, 0.000000, 1.000000}}};
const std::vector<std::vector<array_float_3>> coordinates_NurbsPathCurve{
{{13.690557, 0.000000, 0.000000},
{14.690557, 0.000000, 0.000000},
{15.690557, 0.000000, 0.000000},
{16.690557, 0.000000, 0.000000},
{17.690557, 0.000000, 0.000000}},
{{14.192808, 0.000000, 0.000000},
{14.692808, 0.000000, -1.000000},
{16.692808, 0.000000, -1.000000},
{17.192808, 0.000000, 0.000000}}};
const std::map<std::string, std::unique_ptr<NurbsObject>> all_nurbs_truth = []() {
std::map<std::string, std::unique_ptr<NurbsObject>> all_nurbs;
all_nurbs.emplace(
"NurbsCurve",
/* Name, coordinates, degrees of splines, control points of splines. */
std::make_unique<NurbsObject>(
"NurbsCurve", coordinates_NurbsCurve, std::vector<int>{3}, std::vector<int>{4}));
all_nurbs.emplace(
"NurbsCircle",
std::make_unique<NurbsObject>(
"NurbsCircle", coordinates_NurbsCircle, std::vector<int>{3}, std::vector<int>{11}));
/* This is actually an Object containing a NurbsPath and a NurbsCurve spline. */
all_nurbs.emplace("NurbsPathCurve",
std::make_unique<NurbsObject>("NurbsPathCurve",
coordinates_NurbsPathCurve,
std::vector<int>{3, 3},
std::vector<int>{5, 4}));
return all_nurbs;
}();
} // namespace blender::io::obj

View File

@@ -185,6 +185,9 @@ void WM_operator_properties_filesel(wmOperatorType *ot,
prop = RNA_def_boolean(
ot->srna, "filter_usd", (filter & FILE_TYPE_USD) != 0, "Filter USD files", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_boolean(
ot->srna, "filter_obj", (filter & FILE_TYPE_OBJECT_IO) != 0, "Filter OBJ files", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_boolean(ot->srna,
"filter_volume",
(filter & FILE_TYPE_VOLUME) != 0,