diff --git a/source/blender/editors/io/io_obj.c b/source/blender/editors/io/io_obj.c index ce121203972..e1dba4ee954 100644 --- a/source/blender/editors/io/io_obj.c +++ b/source/blender/editors/io/io_obj.c @@ -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, diff --git a/source/blender/io/wavefront_obj/IO_wavefront_obj.h b/source/blender/io/wavefront_obj/IO_wavefront_obj.h index cf6464eeb37..2c039958bee 100644 --- a/source/blender/io/wavefront_obj/IO_wavefront_obj.h +++ b/source/blender/io/wavefront_obj/IO_wavefront_obj.h @@ -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; diff --git a/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc b/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc index 1a3a333d957..b90a0c99424 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc +++ b/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc @@ -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> &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> &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. */ diff --git a/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc b/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc index 6323b5ba1fc..794d307730c 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc +++ b/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc @@ -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; + } } } diff --git a/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc b/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc index 5f5587577a1..f15687e7bef 100644 --- a/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc +++ b/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc @@ -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 (°_iter_settings, object) { + printf(" {\"%s\", ", object->id.name); + if (object->type == OB_MESH) { + Mesh *mesh = BKE_object_get_evaluated_mesh(object); + const Span 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 (°_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