OBJ: add split by objects/groups import options (T103839)

The new C++ OBJ importer was missing "split by objects" / "split by
groups" import settings of the older Python importer.
Implements T103839.

Added test coverage for all 4 possible combinations of these two
options.
This commit is contained in:
Aras Pranckevicius 2023-01-12 22:47:39 +02:00
parent b2746876f2
commit b599820418
5 changed files with 160 additions and 26 deletions

View File

@ -410,6 +410,8 @@ static int wm_obj_import_exec(bContext *C, wmOperator *op)
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");
import_params.use_split_objects = RNA_boolean_get(op->ptr, "use_split_objects");
import_params.use_split_groups = RNA_boolean_get(op->ptr, "use_split_groups");
import_params.import_vertex_groups = RNA_boolean_get(op->ptr, "import_vertex_groups");
import_params.validate_meshes = RNA_boolean_get(op->ptr, "validate_meshes");
import_params.relative_paths = ((U.flag & USER_RELPATHS) != 0);
@ -472,6 +474,8 @@ static void ui_obj_import_settings(uiLayout *layout, PointerRNA *imfptr)
box = uiLayoutBox(layout);
uiItemL(box, IFACE_("Options"), ICON_EXPORT);
col = uiLayoutColumn(box, false);
uiItemR(col, imfptr, "use_split_objects", 0, NULL, ICON_NONE);
uiItemR(col, imfptr, "use_split_groups", 0, NULL, ICON_NONE);
uiItemR(col, imfptr, "import_vertex_groups", 0, NULL, ICON_NONE);
uiItemR(col, imfptr, "validate_meshes", 0, NULL, ICON_NONE);
}
@ -531,6 +535,16 @@ void WM_OT_obj_import(struct wmOperatorType *ot)
RNA_def_property_update_runtime(prop, (void *)forward_axis_update);
prop = RNA_def_enum(ot->srna, "up_axis", io_transform_axis, IO_AXIS_Y, "Up Axis", "");
RNA_def_property_update_runtime(prop, (void *)up_axis_update);
RNA_def_boolean(ot->srna,
"use_split_objects",
true,
"Split By Object",
"Import each OBJ 'o' as a separate object");
RNA_def_boolean(ot->srna,
"use_split_groups",
false,
"Split By Group",
"Import each OBJ 'g' as a separate object");
RNA_def_boolean(ot->srna,
"import_vertex_groups",
false,

View File

@ -68,6 +68,8 @@ struct OBJImportParams {
float global_scale;
eIOAxis forward_axis;
eIOAxis up_axis;
bool use_split_objects;
bool use_split_groups;
bool import_vertex_groups;
bool validate_meshes;
bool relative_paths;

View File

@ -363,6 +363,24 @@ static void geom_update_smooth_group(const char *p, const char *end, bool &r_sta
r_state_shaded_smooth = smooth != 0;
}
static void geom_new_object(const char *p,
const char *end,
bool &r_state_shaded_smooth,
std::string &r_state_group_name,
int &r_state_material_index,
Geometry *&r_curr_geom,
Vector<std::unique_ptr<Geometry>> &r_all_geometries)
{
r_state_shaded_smooth = false;
r_state_group_name = "";
/* Reset object-local material index that's used in face infos.
* NOTE: do not reset the material name; that has to carry over
* into the next object if needed. */
r_state_material_index = -1;
r_curr_geom = create_geometry(
r_curr_geom, GEOM_MESH, StringRef(p, end).trim(), r_all_geometries);
}
OBJParser::OBJParser(const OBJImportParams &import_params, size_t read_buffer_size = 64 * 1024)
: import_params_(import_params), read_buffer_size_(read_buffer_size)
{
@ -534,22 +552,34 @@ void OBJParser::parse(Vector<std::unique_ptr<Geometry>> &r_all_geometries,
}
/* Objects. */
else if (parse_keyword(p, end, "o")) {
state_shaded_smooth = false;
state_group_name = "";
/* Reset object-local material index that's used in face infos.
* NOTE: do not reset the material name; that has to carry over
* into the next object if needed. */
state_material_index = -1;
curr_geom = create_geometry(
curr_geom, GEOM_MESH, StringRef(p, end).trim(), r_all_geometries);
if (import_params_.use_split_objects) {
geom_new_object(p,
end,
state_shaded_smooth,
state_group_name,
state_material_index,
curr_geom,
r_all_geometries);
}
}
/* Groups. */
else if (parse_keyword(p, end, "g")) {
geom_update_group(StringRef(p, end).trim(), state_group_name);
int new_index = curr_geom->group_indices_.size();
state_group_index = curr_geom->group_indices_.lookup_or_add(state_group_name, new_index);
if (new_index == state_group_index) {
curr_geom->group_order_.append(state_group_name);
if (import_params_.use_split_groups) {
geom_new_object(p,
end,
state_shaded_smooth,
state_group_name,
state_material_index,
curr_geom,
r_all_geometries);
}
else {
geom_update_group(StringRef(p, end).trim(), state_group_name);
int new_index = curr_geom->group_indices_.size();
state_group_index = curr_geom->group_indices_.lookup_or_add(state_group_name, new_index);
if (new_index == state_group_index) {
curr_geom->group_order_.append(state_group_name);
}
}
}
/* Smoothing groups. */

View File

@ -53,7 +53,7 @@ Object *MeshFromGeometry::create_mesh(Main *bmain,
obj->data = BKE_object_obdata_add_from_type(bmain, OB_MESH, ob_name.c_str());
create_vertices(mesh);
create_polys_loops(mesh, import_params.import_vertex_groups);
create_polys_loops(mesh, import_params.import_vertex_groups && !import_params.use_split_groups);
create_edges(mesh);
create_uv_verts(mesh);
create_normals(mesh);
@ -222,8 +222,11 @@ void MeshFromGeometry::create_polys_loops(Mesh *mesh, bool use_vertex_groups)
continue;
}
const int group_index = curr_face.vertex_group_index;
MDeformWeight *dw = BKE_defvert_ensure_index(&dverts[mloop.v], group_index);
dw->weight = 1.0f;
/* Note: face might not belong to any group */
if (group_index >= 0 || 1) {
MDeformWeight *dw = BKE_defvert_ensure_index(&dverts[mloop.v], group_index);
dw->weight = 1.0f;
}
}
}

View File

@ -48,6 +48,19 @@ struct Expectation {
class obj_importer_test : public BlendfileLoadingBaseTest {
public:
obj_importer_test()
{
params.global_scale = 1.0f;
params.clamp_size = 0;
params.forward_axis = IO_AXIS_NEGATIVE_Z;
params.up_axis = IO_AXIS_Y;
params.validate_meshes = true;
params.use_split_objects = true;
params.use_split_groups = false;
params.import_vertex_groups = false;
params.relative_paths = true;
params.clear_selection = true;
}
void import_and_check(const char *path,
const Expectation *expect,
size_t expect_count,
@ -59,16 +72,6 @@ class obj_importer_test : public BlendfileLoadingBaseTest {
return;
}
OBJImportParams params;
params.global_scale = 1.0f;
params.clamp_size = 0;
params.forward_axis = IO_AXIS_NEGATIVE_Z;
params.up_axis = IO_AXIS_Y;
params.validate_meshes = true;
params.import_vertex_groups = false;
params.relative_paths = true;
params.clear_selection = true;
std::string obj_path = blender::tests::flags_test_asset_dir() + "/io_tests/obj/" + path;
strncpy(params.filepath, obj_path.c_str(), FILE_MAX - 1);
const size_t read_buffer_size = 650;
@ -81,6 +84,32 @@ class obj_importer_test : public BlendfileLoadingBaseTest {
deg_iter_settings.flags = DEG_ITER_OBJECT_FLAG_LINKED_DIRECTLY |
DEG_ITER_OBJECT_FLAG_LINKED_VIA_SET | DEG_ITER_OBJECT_FLAG_VISIBLE |
DEG_ITER_OBJECT_FLAG_DUPLI;
constexpr bool print_result_scene = false;
if (print_result_scene) {
printf("Result was:\n");
DEG_OBJECT_ITER_BEGIN (&deg_iter_settings, object) {
printf(" {\"%s\", ", object->id.name);
if (object->type == OB_MESH) {
Mesh *mesh = BKE_object_get_evaluated_mesh(object);
const Span<float3> positions = mesh->vert_positions();
printf("OB_MESH, %i, %i, %i, %i, float3(%g, %g, %g), float3(%g, %g, %g)",
mesh->totvert,
mesh->totedge,
mesh->totpoly,
mesh->totloop,
positions.first().x,
positions.first().y,
positions.first().z,
positions.last().x,
positions.last().y,
positions.last().z);
}
printf("},\n");
}
DEG_OBJECT_ITER_END;
}
size_t object_index = 0;
DEG_OBJECT_ITER_BEGIN (&deg_iter_settings, object) {
if (object_index >= expect_count) {
@ -152,6 +181,8 @@ class obj_importer_test : public BlendfileLoadingBaseTest {
const int ima_count = BLI_listbase_count(&bfile->main->images);
EXPECT_EQ(ima_count, expect_image_count);
}
OBJImportParams params;
};
TEST_F(obj_importer_test, import_cube)
@ -784,4 +815,58 @@ TEST_F(obj_importer_test, import_vertices)
import_and_check("vertices.obj", expect, std::size(expect), 0);
}
TEST_F(obj_importer_test, import_split_options_by_object)
{
/* Default is to split by object */
Expectation expect[] = {
{"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)},
{"OBBox", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, -1, 1)},
{"OBPyramid", OB_MESH, 5, 8, 5, 16, float3(3, 1, -1), float3(4, 0, 2)},
};
import_and_check("split_options.obj", expect, std::size(expect), 0);
}
TEST_F(obj_importer_test, import_split_options_by_group)
{
params.use_split_objects = false;
params.use_split_groups = true;
Expectation expect[] = {
{"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)},
{"OBBoxOne", OB_MESH, 4, 4, 1, 4, float3(1, -1, -1), float3(-1, -1, 1)},
{"OBBoxTwo", OB_MESH, 6, 7, 2, 8, float3(1, 1, 1), float3(-1, -1, 1)},
{"OBBoxTwo.001", OB_MESH, 6, 7, 2, 8, float3(1, 1, -1), float3(-1, -1, -1)},
{"OBPyrBottom", OB_MESH, 4, 4, 1, 4, float3(3, 1, -1), float3(3, -1, -1)},
{"OBPyrSides", OB_MESH, 5, 8, 4, 12, float3(3, 1, -1), float3(4, 0, 2)},
{"OBsplit_options", OB_MESH, 4, 4, 1, 4, float3(1, 1, -1), float3(-1, 1, 1)},
};
import_and_check("split_options.obj", expect, std::size(expect), 0);
}
TEST_F(obj_importer_test, import_split_options_by_object_and_group)
{
params.use_split_objects = true;
params.use_split_groups = true;
Expectation expect[] = {
{"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)},
{"OBBox", OB_MESH, 4, 4, 1, 4, float3(1, 1, -1), float3(-1, 1, 1)},
{"OBBoxOne", OB_MESH, 4, 4, 1, 4, float3(1, -1, -1), float3(-1, -1, 1)},
{"OBBoxTwo", OB_MESH, 6, 7, 2, 8, float3(1, 1, 1), float3(-1, -1, 1)},
{"OBBoxTwo.001", OB_MESH, 6, 7, 2, 8, float3(1, 1, -1), float3(-1, -1, -1)},
{"OBPyrBottom", OB_MESH, 4, 4, 1, 4, float3(3, 1, -1), float3(3, -1, -1)},
{"OBPyrSides", OB_MESH, 5, 8, 4, 12, float3(3, 1, -1), float3(4, 0, 2)},
};
import_and_check("split_options.obj", expect, std::size(expect), 0);
}
TEST_F(obj_importer_test, import_split_options_none)
{
params.use_split_objects = false;
params.use_split_groups = false;
Expectation expect[] = {
{"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)},
{"OBsplit_options", OB_MESH, 13, 20, 11, 40, float3(1, 1, -1), float3(4, 0, 2)},
};
import_and_check("split_options.obj", expect, std::size(expect), 0);
}
} // namespace blender::io::obj