Compare commits
455 Commits
node-group
...
soc-2020-i
Author | SHA1 | Date | |
---|---|---|---|
e9bbfd0c8c | |||
1aa953bd19 | |||
fc171c1be9 | |||
96c80950a1 | |||
6e9eebe286 | |||
53e7a2cb07 | |||
e3edf862a9 | |||
5f9eea200d | |||
4a7eca10ab | |||
d619c62157 | |||
1eed0031ec | |||
fef2b9e2eb | |||
db254aa981 | |||
40027f31b3 | |||
2cb056af9a | |||
22686b4ccf | |||
984ab4719d | |||
51ea8dc16d | |||
7a60565e54 | |||
30ee3a1d5d | |||
13be5a0e9b | |||
1100dd8ad1 | |||
ab97b22a1a | |||
b3adbd8ed8 | |||
b985418557 | |||
fec16d4a8b | |||
ccc97b664c | |||
bfea06a639 | |||
6af509f5a5 | |||
85ab8eaa88 | |||
dc68998aad | |||
8268e687b0 | |||
7c603e6928 | |||
eb16cebee4 | |||
bee5c9720d | |||
94dac497b2 | |||
8abffed35a | |||
3ce42cb959 | |||
9abc78c3f2 | |||
a9cf404223 | |||
29d5149843 | |||
30def30420 | |||
10c8b88cf1 | |||
5280200275 | |||
ad40a7c3d6 | |||
d6f0cf048d | |||
7b4bb69866 | |||
fc71aaa487 | |||
c59a5002e2 | |||
5c48127877 | |||
32dad88e8f | |||
7206226ee0 | |||
b54aa8a77c | |||
7faaaf7fd7 | |||
01fa2a239c | |||
6cc5e59b49 | |||
06d61d459f | |||
f4b22244ca | |||
df6fe191a9 | |||
d6f4c1f0d3 | |||
9fc8ec7791 | |||
65b7d952ff | |||
e5a97df7b5 | |||
4b40cce671 | |||
19f926f593 | |||
ae8b87e757 | |||
7859e3c280 | |||
43c1e37d45 | |||
d1492ebb6d | |||
36904f01a6 | |||
44629501fe | |||
533964ca7d | |||
1c3d4ace05 | |||
57ae71dbf3 | |||
6ca3ca23de | |||
25bac75fae | |||
bc52869756 | |||
347a12ba4f | |||
1223782976 | |||
30cc8b8567 | |||
9df5b84b1b | |||
60b5a8ab62 | |||
8a785ee54f | |||
b046126ac5 | |||
5d3533f4d5 | |||
4e8c2cfd1c | |||
0bfa68c901 | |||
d58b2c258b | |||
f830f7cdef | |||
4db25c833a | |||
16c51ce148 | |||
af2161f901 | |||
5b3c7458e9 | |||
2761a59387 | |||
9f14881e61 | |||
3f94db1af2 | |||
53e53c1123 | |||
447d2b5b12 | |||
3d12e92200 | |||
d0bb2c6277 | |||
9554e93efd | |||
f52023a85c | |||
f5d986822d | |||
7f854c4bc4 | |||
2ace7521e3 | |||
670c103f10 | |||
183c4af692 | |||
85f809ca39 | |||
f1a60130bd | |||
b3f08db450 | |||
4f041c98fc | |||
0af469d131 | |||
4c8253a966 | |||
9c622b9223 | |||
1a658978e5 | |||
c2305201bc | |||
61ad01ec0c | |||
f0de7b58bf | |||
dbd7c31f86 | |||
ac2b609e4e | |||
d5b0467732 | |||
84d32cb5c5 | |||
55bc6ac173 | |||
9e04dc90c7 | |||
78bbdbd931 | |||
0444cabb41 | |||
33bd98a97b | |||
8b316eb7a8 | |||
bc2a8f01ba | |||
aaa4a544ae | |||
ef8b8f4030 | |||
2e16d14cee | |||
942039244e | |||
5ab177be32 | |||
6570780a79 | |||
e96dd70fac | |||
b6b4aa99ff | |||
ded6377ea0 | |||
3e544770f6 | |||
8d5101ce1d | |||
8ce2fdee8b | |||
9e15728380 | |||
c1c1e3ea88 | |||
d1cc8c4712 | |||
fb75c957de | |||
83e0db031a | |||
9ea4a50fee | |||
a45112a9ec | |||
dfeabd0032 | |||
0f99e37602 | |||
1d19c96a1f | |||
641f859f88 | |||
5e54c8a0d5 | |||
047189baf0 | |||
8b3f87f9f7 | |||
b7689c0083 | |||
b99246c613 | |||
e3e01ae8a2 | |||
82b4639477 | |||
c5f984d96c | |||
b136e11526 | |||
52d9d6cd21 | |||
d67476a83f | |||
1a0909b15d | |||
2ce3ffb306 | |||
159a6bc537 | |||
30e48c58ba | |||
5c3ec6245e | |||
a742bede2b | |||
3bffab7a07 | |||
bc78649c01 | |||
eaf54f9eb7 | |||
31d6df6d35 | |||
f4c70d7c9f | |||
fd529c01c9 | |||
0af083449e | |||
448dc2da2b | |||
67a147d2c4 | |||
6a5b147a9f | |||
8279501af5 | |||
b96aec3691 | |||
5117948285 | |||
6c8f6d5c44 | |||
6f7d03b643 | |||
d0e91f78fa | |||
83ac5a0f5f | |||
85b1234107 | |||
21a097f952 | |||
6b62935e89 | |||
24a9729921 | |||
008eb7af41 | |||
3c4a67c575 | |||
54f15a66a0 | |||
0ba0c3653b | |||
40a2caca86 | |||
be046b01d1 | |||
7a5d794862 | |||
9445be11fb | |||
0b9f41f4a0 | |||
a6ff8534f2 | |||
c666a4c03c | |||
e715c482cc | |||
c239d5f3b0 | |||
8290d2d15c | |||
46ff12a5d2 | |||
5be5f3b4bb | |||
387c58847d | |||
80abfd7d55 | |||
fc816d358e | |||
87f407d679 | |||
0aeb338d19 | |||
9ac8def136 | |||
a662ddd76a | |||
8c6a06e730 | |||
32fe286919 | |||
b22b962ca2 | |||
6cd247c277 | |||
73454388fb | |||
ae122533e7 | |||
4777a6a54a | |||
d514de67e6 | |||
edd5307e9e | |||
10e3f23ba3 | |||
e76ab12454 | |||
5b0cb5bbbe | |||
efab0bc704 | |||
69270c7800 | |||
0c7801fa2b | |||
3f8a8d1757 | |||
c583afd60b | |||
0479dc410f | |||
4923087d25 | |||
491bd8b8bb | |||
c815ba0a3c | |||
c5e0e82f68 | |||
a732abd99b | |||
99ededd947 | |||
ae1c5f16cb | |||
d882a63e98 | |||
69f70829d2 | |||
ba0d376e26 | |||
d9cdfba21e | |||
a725b6f7ac | |||
97aeaf8dde | |||
d5866e8d74 | |||
606b0d7da3 | |||
d2a798d29f | |||
c010cf5814 | |||
e0c0b8ffd1 | |||
bb2eca07a6 | |||
b378f84f7c | |||
009b37719d | |||
82eff7a025 | |||
d062c712e2 | |||
a0f21e47ae | |||
d2fcc7b48c | |||
6ee696e5bf | |||
9b37f94324 | |||
7f2893848e | |||
8598993157 | |||
9616e2ef78 | |||
6e419e95e7 | |||
0a339bb5e7 | |||
5fa1e0eb38 | |||
3cff0de435 | |||
27cebf309f | |||
06336941b3 | |||
2c20b379f9 | |||
d081bf97df | |||
befe950f18 | |||
1b1727ea6a | |||
d660455882 | |||
8339dd6647 | |||
d6f9400417 | |||
928736b173 | |||
9b8f2042b0 | |||
f9348be84f | |||
b8e4d4c6da | |||
d5ad01edf3 | |||
64ff38a7a7 | |||
722a793b74 | |||
8e58bd0996 | |||
a26657cdb8 | |||
c401d8a0ae | |||
48d3582196 | |||
9433124290 | |||
7b77d88275 | |||
19145856ba | |||
c5bd1631f6 | |||
a59bbede21 | |||
78f29f6c0a | |||
b33a4592a3 | |||
ffaa1df439 | |||
37467ec5e9 | |||
5898f6ef1f | |||
af278ce58b | |||
25526821a4 | |||
4ec4f5b309 | |||
ed8c902a6a | |||
2bb56c83fa | |||
4716660591 | |||
97aa9d44fa | |||
cab598f2b2 | |||
31b7a53605 | |||
7bd38c2776 | |||
95716b7681 | |||
6e21f8c20d | |||
9cb750ba66 | |||
b9718a4795 | |||
a7f5998550 | |||
71eadb4b62 | |||
5468cc0aae | |||
7139d216f3 | |||
dce0a628a2 | |||
65fd3be1fa | |||
5a9b983263 | |||
37d59dbc8c | |||
e33d8f79a9 | |||
ef5941f31a | |||
fa0daf9a3e | |||
151e882512 | |||
fe4c5350c4 | |||
c275b2784b | |||
aab8982f9e | |||
92be92befe | |||
031c4732f4 | |||
501ead4bbc | |||
81d46ef2bd | |||
a4a1184ece | |||
d68899e99a | |||
aa9e4b23e5 | |||
aacb1f4756 | |||
5e9196ed11 | |||
4c9b344a5d | |||
ec04edfe5c | |||
3cfcf37cea | |||
f8d64b396d | |||
582bf4397c | |||
7582bbd574 | |||
f7c2fb187d | |||
c4cec5e52c | |||
7b123fec1c | |||
3e30be30ac | |||
e3fb4d0dd6 | |||
fc58522598 | |||
d00d2bd308 | |||
e9aa1b92d6 | |||
a10942814a | |||
f4abd34699 | |||
5299b52d7a | |||
d14811e517 | |||
928f5c9b9a | |||
550b7cfba0 | |||
e517cc0a06 | |||
a344a1cbe8 | |||
d7f9a627f6 | |||
639d512369 | |||
e072382976 | |||
e061df6497 | |||
fe3a359fb1 | |||
c2eb16f662 | |||
68807cf466 | |||
d85c2620b8 | |||
91072928cd | |||
42a8ea1af3 | |||
fd4e5563fd | |||
5a1ecb1702 | |||
df4e43d9fd | |||
5ee4aa5744 | |||
242df25b28 | |||
3fb230d6fe | |||
012175f843 | |||
71c6d384c1 | |||
905f470598 | |||
2585882973 | |||
9279377f4b | |||
054f3981b1 | |||
fdac45d68e | |||
e3e5ae5da8 | |||
6343c98a8e | |||
c2e43ca99a | |||
ddbc43afe0 | |||
35dc587fd6 | |||
469398155d | |||
02fa1f0bba | |||
faa11ec04a | |||
bab0fce914 | |||
9086989744 | |||
7685d9e450 | |||
9c6e3e103a | |||
6529b3a09e | |||
2e12333c19 | |||
cfa167d57f | |||
b22f8aec2b | |||
d724bf3797 | |||
c39128ca97 | |||
360ea0a715 | |||
eadc23273e | |||
11e63662be | |||
96d6571073 | |||
eb56b73895 | |||
827869a45b | |||
6c98925d5a | |||
42f24a134c | |||
dd92c95ea7 | |||
ef0eff0b8a | |||
31d480174a | |||
12fbd19ee7 | |||
0f383a3d7c | |||
117c990540 | |||
d13a05a6cf | |||
3a61338a09 | |||
f2a1a66b8c | |||
0d20c0a6ea | |||
f9289bb5ea | |||
c4c1d7b0b9 | |||
f3342b4bfc | |||
11357e18f7 | |||
b8e80fbce1 | |||
c3f8e9550c | |||
41f47cd8d6 | |||
d47318fb45 | |||
4ad57b2d7f | |||
1d75ece6ad | |||
4a59ba4bc2 | |||
4546101237 | |||
a79618a719 | |||
d21b3ff680 | |||
6d088bdde4 | |||
ad5b8bd899 | |||
c161c69179 | |||
40736795ec | |||
387d96294a | |||
3cbd4516c6 | |||
c2fbfb421b | |||
f01a7d508d | |||
37074c26df | |||
0ea70ab8e5 | |||
86845ea907 | |||
7af1ec516f | |||
b234b2fcf1 | |||
02c7cf5322 | |||
083e110c49 | |||
5f951f96b6 | |||
472c67eec2 | |||
7c19ab2c61 | |||
485cc4330a | |||
3c947bd5a6 | |||
7294f0ce3d | |||
4864f7e281 | |||
3e3cee15bf | |||
475f15210d | |||
e2d9b9fd6a | |||
3cfb3360ca | |||
781b74589a |
@@ -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)")
|
||||
|
@@ -26,6 +26,7 @@ set(INC
|
||||
../blentranslation
|
||||
../depsgraph
|
||||
../draw
|
||||
../editors/include
|
||||
../imbuf
|
||||
../makesdna
|
||||
../makesrna
|
||||
|
@@ -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();
|
||||
|
||||
|
@@ -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)
|
||||
|
456
source/blender/editors/io/io_obj.c
Normal file
456
source/blender/editors/io/io_obj.c
Normal 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", "");
|
||||
}
|
29
source/blender/editors/io/io_obj.h
Normal file
29
source/blender/editors/io/io_obj.h
Normal 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);
|
@@ -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);
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -19,6 +19,7 @@
|
||||
# ***** END GPL LICENSE BLOCK *****
|
||||
|
||||
add_subdirectory(common)
|
||||
add_subdirectory(wavefront_obj)
|
||||
|
||||
if(WITH_ALEMBIC)
|
||||
add_subdirectory(alembic)
|
||||
|
104
source/blender/io/wavefront_obj/CMakeLists.txt
Normal file
104
source/blender/io/wavefront_obj/CMakeLists.txt
Normal 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()
|
47
source/blender/io/wavefront_obj/IO_wavefront_obj.cc
Normal file
47
source/blender/io/wavefront_obj/IO_wavefront_obj.cc
Normal 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);
|
||||
}
|
111
source/blender/io/wavefront_obj/IO_wavefront_obj.h
Normal file
111
source/blender/io/wavefront_obj/IO_wavefront_obj.h
Normal 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
|
@@ -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 smooth–shaded, 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
|
@@ -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
|
343
source/blender/io/wavefront_obj/exporter/obj_export_io.hh
Normal file
343
source/blender/io/wavefront_obj/exporter/obj_export_io.hh
Normal 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
|
490
source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc
Normal file
490
source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc
Normal 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
|
134
source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh
Normal file
134
source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh
Normal 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
|
365
source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc
Normal file
365
source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc
Normal 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
|
107
source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh
Normal file
107
source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh
Normal 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
|
125
source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc
Normal file
125
source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc
Normal 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
|
60
source/blender/io/wavefront_obj/exporter/obj_export_nurbs.hh
Normal file
60
source/blender/io/wavefront_obj/exporter/obj_export_nurbs.hh
Normal 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
|
310
source/blender/io/wavefront_obj/exporter/obj_exporter.cc
Normal file
310
source/blender/io/wavefront_obj/exporter/obj_exporter.cc
Normal 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
|
82
source/blender/io/wavefront_obj/exporter/obj_exporter.hh
Normal file
82
source/blender/io/wavefront_obj/exporter/obj_exporter.hh
Normal 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
|
368
source/blender/io/wavefront_obj/importer/importer_mesh_utils.cc
Normal file
368
source/blender/io/wavefront_obj/importer/importer_mesh_utils.cc
Normal 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
|
@@ -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
|
@@ -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
|
@@ -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
|
413
source/blender/io/wavefront_obj/importer/obj_import_mesh.cc
Normal file
413
source/blender/io/wavefront_obj/importer/obj_import_mesh.cc
Normal 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
|
94
source/blender/io/wavefront_obj/importer/obj_import_mesh.hh
Normal file
94
source/blender/io/wavefront_obj/importer/obj_import_mesh.hh
Normal 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
|
377
source/blender/io/wavefront_obj/importer/obj_import_mtl.cc
Normal file
377
source/blender/io/wavefront_obj/importer/obj_import_mtl.cc
Normal 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
|
115
source/blender/io/wavefront_obj/importer/obj_import_mtl.hh
Normal file
115
source/blender/io/wavefront_obj/importer/obj_import_mtl.hh
Normal 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
|
123
source/blender/io/wavefront_obj/importer/obj_import_nurbs.cc
Normal file
123
source/blender/io/wavefront_obj/importer/obj_import_nurbs.cc
Normal 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
|
86
source/blender/io/wavefront_obj/importer/obj_import_nurbs.hh
Normal file
86
source/blender/io/wavefront_obj/importer/obj_import_nurbs.hh
Normal 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
|
155
source/blender/io/wavefront_obj/importer/obj_import_objects.cc
Normal file
155
source/blender/io/wavefront_obj/importer/obj_import_objects.cc
Normal 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
|
183
source/blender/io/wavefront_obj/importer/obj_import_objects.hh
Normal file
183
source/blender/io/wavefront_obj/importer/obj_import_objects.hh
Normal 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
|
91
source/blender/io/wavefront_obj/importer/obj_importer.cc
Normal file
91
source/blender/io/wavefront_obj/importer/obj_importer.cc
Normal 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
|
31
source/blender/io/wavefront_obj/importer/obj_importer.hh
Normal file
31
source/blender/io/wavefront_obj/importer/obj_importer.hh
Normal 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
|
217
source/blender/io/wavefront_obj/importer/parser_string_utils.cc
Normal file
217
source/blender/io/wavefront_obj/importer/parser_string_utils.cc
Normal 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
|
@@ -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
|
416
source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc
Normal file
416
source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc
Normal 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 ¶ms,
|
||||
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 ¶ms)
|
||||
{
|
||||
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
|
149
source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh
Normal file
149
source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh
Normal 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
|
@@ -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,
|
||||
|
Reference in New Issue
Block a user