diff options
Diffstat (limited to 'source/blender/io')
69 files changed, 2017 insertions, 1646 deletions
diff --git a/source/blender/io/alembic/ABC_alembic.h b/source/blender/io/alembic/ABC_alembic.h index ded3258ff18..05025861857 100644 --- a/source/blender/io/alembic/ABC_alembic.h +++ b/source/blender/io/alembic/ABC_alembic.h @@ -60,6 +60,28 @@ struct AlembicExportParams { float global_scale; }; +struct AlembicImportParams { + /* Multiplier for the cached data scale. Mostly useful if the data is stored in a different unit + * as what Blender expects (e.g. centimeters instead of meters). */ + float global_scale; + + /* Number of consecutive files to expect if the cached animation is split in a sequence. */ + int sequence_len; + /* Start frame of the sequence, offset from 0. */ + int sequence_offset; + /* True if the cache is split in multiple files. */ + bool is_sequence; + + /* True if the importer should set the current scene's start and end frame based on the start and + * end frames of the cached animation. */ + bool set_frame_range; + /* True if imported meshes should be validated. Error messages are sent to the console. */ + bool validate_meshes; + /* True if a cache reader should be added regardless of whether there is animated data in the + * cached file. */ + bool always_add_cache_reader; +}; + /* The ABC_export and ABC_import functions both take a as_background_job * parameter, and return a boolean. * @@ -78,13 +100,7 @@ bool ABC_export(struct Scene *scene, bool ABC_import(struct bContext *C, const char *filepath, - float scale, - bool is_sequence, - bool set_frame_range, - int sequence_len, - int offset, - bool validate_meshes, - bool always_add_cache_reader, + const struct AlembicImportParams *params, bool as_background_job); struct CacheArchiveHandle *ABC_create_handle(struct Main *bmain, diff --git a/source/blender/io/alembic/exporter/abc_export_capi.cc b/source/blender/io/alembic/exporter/abc_export_capi.cc index edaf53b3efa..dfca89e2c6d 100644 --- a/source/blender/io/alembic/exporter/abc_export_capi.cc +++ b/source/blender/io/alembic/exporter/abc_export_capi.cc @@ -24,6 +24,7 @@ #include "BLI_fileops.h" #include "BLI_path_util.h" #include "BLI_string.h" +#include "BLI_timeit.hh" #include "WM_api.h" #include "WM_types.h" @@ -44,6 +45,7 @@ struct ExportJobData { bool was_canceled; bool export_ok; + blender::timeit::TimePoint start_time; }; namespace blender::io::alembic { @@ -59,6 +61,14 @@ static void build_depsgraph(Depsgraph *depsgraph, const bool visible_objects_onl } } +static void report_job_duration(const ExportJobData *data) +{ + blender::timeit::Nanoseconds duration = blender::timeit::Clock::now() - data->start_time; + std::cout << "Alembic export of '" << data->filename << "' took "; + blender::timeit::print_duration(duration); + std::cout << '\n'; +} + static void export_startjob(void *customdata, /* Cannot be const, this function implements wm_jobs_start_callback. * NOLINTNEXTLINE: readability-non-const-parameter. */ @@ -68,6 +78,7 @@ static void export_startjob(void *customdata, { ExportJobData *data = static_cast<ExportJobData *>(customdata); data->was_canceled = false; + data->start_time = blender::timeit::Clock::now(); G.is_rendering = true; WM_set_locked_interface(data->wm, true); @@ -85,7 +96,7 @@ static void export_startjob(void *customdata, /* For restoring the current frame after exporting animation is done. */ Scene *scene = DEG_get_input_scene(data->depsgraph); - const int orig_frame = CFRA; + const int orig_frame = scene->r.cfra; const bool export_animation = (data->params.frame_start != data->params.frame_end); /* Create the Alembic archive. */ @@ -154,8 +165,8 @@ static void export_startjob(void *customdata, iter.release_writers(); /* Finish up by going back to the keyframe that was current before we started. */ - if (CFRA != orig_frame) { - CFRA = orig_frame; + if (scene->r.cfra != orig_frame) { + scene->r.cfra = orig_frame; BKE_scene_graph_update_for_newframe(data->depsgraph); } @@ -177,6 +188,7 @@ static void export_endjob(void *customdata) G.is_rendering = false; WM_set_locked_interface(data->wm, false); + report_job_duration(data); } } // namespace blender::io::alembic diff --git a/source/blender/io/alembic/exporter/abc_writer_hair.cc b/source/blender/io/alembic/exporter/abc_writer_hair.cc index d12eaf07e29..4f09aee3ea9 100644 --- a/source/blender/io/alembic/exporter/abc_writer_hair.cc +++ b/source/blender/io/alembic/exporter/abc_writer_hair.cc @@ -18,6 +18,7 @@ #include "BKE_customdata.h" #include "BKE_mesh.h" +#include "BKE_mesh_legacy_convert.h" #include "BKE_mesh_runtime.h" #include "BKE_particle.h" @@ -119,9 +120,9 @@ void ABCHairWriter::write_hair_sample(const HierarchyContext &context, float inv_mat[4][4]; invert_m4_m4_safe(inv_mat, context.object->obmat); - MTFace *mtface = mesh->mtface; - MFace *mface = mesh->mface; - MVert *mverts = mesh->mvert; + MTFace *mtface = (MTFace *)CustomData_get_layer(&mesh->fdata, CD_MTFACE); + MFace *mface = (MFace *)CustomData_get_layer(&mesh->fdata, CD_MFACE); + const MVert *mverts = BKE_mesh_verts(mesh); const float(*vert_normals)[3] = BKE_mesh_vertex_normals_ensure(mesh); if ((!mtface || !mface) && !uv_warning_shown_) { @@ -242,8 +243,9 @@ void ABCHairWriter::write_hair_child_sample(const HierarchyContext &context, float inv_mat[4][4]; invert_m4_m4_safe(inv_mat, context.object->obmat); - MTFace *mtface = mesh->mtface; - MVert *mverts = mesh->mvert; + MFace *mface = (MFace *)CustomData_get_layer(&mesh->fdata, CD_MFACE); + MTFace *mtface = (MTFace *)CustomData_get_layer(&mesh->fdata, CD_MTFACE); + const MVert *mverts = BKE_mesh_verts(mesh); const float(*vert_normals)[3] = BKE_mesh_vertex_normals_ensure(mesh); ParticleSystem *psys = context.particle_system; @@ -268,7 +270,7 @@ void ABCHairWriter::write_hair_child_sample(const HierarchyContext &context, continue; } - MFace *face = &mesh->mface[num]; + MFace *face = &mface[num]; MTFace *tface = mtface + num; float r_uv[2], tmpnor[3], mapfw[4], vec[3]; diff --git a/source/blender/io/alembic/exporter/abc_writer_mesh.cc b/source/blender/io/alembic/exporter/abc_writer_mesh.cc index 07b185ffd64..7d38cd1ec88 100644 --- a/source/blender/io/alembic/exporter/abc_writer_mesh.cc +++ b/source/blender/io/alembic/exporter/abc_writer_mesh.cc @@ -12,6 +12,7 @@ #include "BLI_math_vector.h" #include "BKE_attribute.h" +#include "BKE_attribute.hh" #include "BKE_customdata.h" #include "BKE_lib_id.h" #include "BKE_material.h" @@ -175,8 +176,8 @@ void ABCGenericMeshWriter::do_write(HierarchyContext &context) m_custom_data_config.pack_uvs = args_.export_params->packuv; m_custom_data_config.mesh = mesh; - m_custom_data_config.mpoly = mesh->mpoly; - m_custom_data_config.mloop = mesh->mloop; + m_custom_data_config.mpoly = mesh->polys_for_write().data(); + m_custom_data_config.mloop = mesh->loops_for_write().data(); m_custom_data_config.totpoly = mesh->totpoly; m_custom_data_config.totloop = mesh->totloop; m_custom_data_config.totvert = mesh->totvert; @@ -366,7 +367,7 @@ bool ABCGenericMeshWriter::get_velocities(struct Mesh *mesh, std::vector<Imath:: { /* Export velocity attribute output by fluid sim, sequence cache modifier * and geometry nodes. */ - CustomDataLayer *velocity_layer = BKE_id_attribute_find( + const CustomDataLayer *velocity_layer = BKE_id_attribute_find( &mesh->id, "velocity", CD_PROP_FLOAT3, ATTR_DOMAIN_POINT); if (velocity_layer == nullptr) { @@ -390,12 +391,12 @@ void ABCGenericMeshWriter::get_geo_groups(Object *object, struct Mesh *mesh, std::map<std::string, std::vector<int32_t>> &geo_groups) { - const int num_poly = mesh->totpoly; - MPoly *polygons = mesh->mpoly; + const bke::AttributeAccessor attributes = mesh->attributes(); + const VArraySpan<int> material_indices = attributes.lookup_or_default<int>( + "material_index", ATTR_DOMAIN_FACE, 0); - for (int i = 0; i < num_poly; i++) { - MPoly ¤t_poly = polygons[i]; - short mnr = current_poly.mat_nr; + for (const int i : material_indices.index_range()) { + short mnr = material_indices[i]; Material *mat = BKE_object_material_get(object, mnr + 1); @@ -435,8 +436,7 @@ static void get_vertices(struct Mesh *mesh, std::vector<Imath::V3f> &points) points.clear(); points.resize(mesh->totvert); - MVert *verts = mesh->mvert; - + const Span<MVert> verts = mesh->verts(); for (int i = 0, e = mesh->totvert; i < e; i++) { copy_yup_from_zup(points[i].getValue(), verts[i].co); } @@ -447,25 +447,23 @@ static void get_topology(struct Mesh *mesh, std::vector<int32_t> &loop_counts, bool &r_has_flat_shaded_poly) { - const int num_poly = mesh->totpoly; - const int num_loops = mesh->totloop; - MLoop *mloop = mesh->mloop; - MPoly *mpoly = mesh->mpoly; + const Span<MPoly> polys = mesh->polys(); + const Span<MLoop> loops = mesh->loops(); r_has_flat_shaded_poly = false; poly_verts.clear(); loop_counts.clear(); - poly_verts.reserve(num_loops); - loop_counts.reserve(num_poly); + poly_verts.reserve(loops.size()); + loop_counts.reserve(polys.size()); /* NOTE: data needs to be written in the reverse order. */ - for (int i = 0; i < num_poly; i++) { - MPoly &poly = mpoly[i]; + for (const int i : polys.index_range()) { + const MPoly &poly = polys[i]; loop_counts.push_back(poly.totloop); r_has_flat_shaded_poly |= (poly.flag & ME_SMOOTH) == 0; - MLoop *loop = mloop + poly.loopstart + (poly.totloop - 1); + const MLoop *loop = &loops[poly.loopstart + (poly.totloop - 1)]; for (int j = 0; j < poly.totloop; j++, loop--) { poly_verts.push_back(loop->v); @@ -484,14 +482,14 @@ static void get_edge_creases(struct Mesh *mesh, lengths.clear(); sharpnesses.clear(); - MEdge *edge = mesh->medge; + const Span<MEdge> edges = mesh->edges(); - for (int i = 0, e = mesh->totedge; i < e; i++) { - const float sharpness = static_cast<float>(edge[i].crease) * factor; + for (const int i : edges.index_range()) { + const float sharpness = static_cast<float>(edges[i].crease) * factor; if (sharpness != 0.0f) { - indices.push_back(edge[i].v1); - indices.push_back(edge[i].v2); + indices.push_back(edges[i].v1); + indices.push_back(edges[i].v2); sharpnesses.push_back(sharpness); } } @@ -543,8 +541,10 @@ static void get_loop_normals(struct Mesh *mesh, /* NOTE: data needs to be written in the reverse order. */ int abc_index = 0; - MPoly *mp = mesh->mpoly; - for (int i = 0, e = mesh->totpoly; i < e; i++, mp++) { + const Span<MPoly> polys = mesh->polys(); + + for (const int i : polys.index_range()) { + const MPoly *mp = &polys[i]; for (int j = mp->totloop - 1; j >= 0; j--, abc_index++) { int blender_index = mp->loopstart + j; copy_yup_from_zup(normals[abc_index].getValue(), lnors[blender_index]); diff --git a/source/blender/io/alembic/intern/abc_customdata.cc b/source/blender/io/alembic/intern/abc_customdata.cc index 2820a128072..5494bfaa6e8 100644 --- a/source/blender/io/alembic/intern/abc_customdata.cc +++ b/source/blender/io/alembic/intern/abc_customdata.cc @@ -57,7 +57,7 @@ static void get_uvs(const CDStreamConfig &config, } const int num_poly = config.totpoly; - MPoly *polygons = config.mpoly; + MPoly *mpoly = config.mpoly; MLoop *mloop = config.mloop; if (!config.pack_uvs) { @@ -67,7 +67,7 @@ static void get_uvs(const CDStreamConfig &config, /* Iterate in reverse order to match exported polygons. */ for (int i = 0; i < num_poly; i++) { - MPoly ¤t_poly = polygons[i]; + MPoly ¤t_poly = mpoly[i]; const MLoopUV *loopuv = mloopuv_array + current_poly.loopstart + current_poly.totloop; for (int j = 0; j < current_poly.totloop; j++, count++) { @@ -85,7 +85,7 @@ static void get_uvs(const CDStreamConfig &config, int idx_count = 0; for (int i = 0; i < num_poly; i++) { - MPoly ¤t_poly = polygons[i]; + MPoly ¤t_poly = mpoly[i]; MLoop *looppoly = mloop + current_poly.loopstart + current_poly.totloop; const MLoopUV *loopuv = mloopuv_array + current_poly.loopstart + current_poly.totloop; @@ -540,7 +540,7 @@ void read_generated_coordinates(const ICompoundProperty &prop, cd_data = CustomData_get_layer(&mesh->vdata, CD_ORCO); } else { - cd_data = CustomData_add_layer(&mesh->vdata, CD_ORCO, CD_CALLOC, nullptr, totvert); + cd_data = CustomData_add_layer(&mesh->vdata, CD_ORCO, CD_CONSTRUCT, nullptr, totvert); } float(*orcodata)[3] = static_cast<float(*)[3]>(cd_data); @@ -564,7 +564,6 @@ void read_custom_data(const std::string &iobject_full_name, } int num_uvs = 0; - int num_colors = 0; const size_t num_props = prop.getNumProperties(); @@ -583,10 +582,6 @@ void read_custom_data(const std::string &iobject_full_name, /* Read vertex colors according to convention. */ if (IC3fGeomParam::matches(prop_header) || IC4fGeomParam::matches(prop_header)) { - if (++num_colors > MAX_MCOL) { - continue; - } - read_custom_data_mcols(iobject_full_name, prop, prop_header, config, iss); continue; } diff --git a/source/blender/io/alembic/intern/abc_reader_mesh.cc b/source/blender/io/alembic/intern/abc_reader_mesh.cc index d8c48357fc0..65d99e3f057 100644 --- a/source/blender/io/alembic/intern/abc_reader_mesh.cc +++ b/source/blender/io/alembic/intern/abc_reader_mesh.cc @@ -20,11 +20,13 @@ #include "DNA_object_types.h" #include "BLI_compiler_compat.h" +#include "BLI_edgehash.h" #include "BLI_index_range.hh" #include "BLI_listbase.h" #include "BLI_math_geom.h" -#include "BKE_attribute.h" +#include "BKE_attribute.hh" +#include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_material.h" #include "BKE_mesh.h" @@ -77,10 +79,8 @@ static void assign_materials(Main *bmain, const std::map<std::string, int> &mat_index_map) { std::map<std::string, int>::const_iterator it; - for (it = mat_index_map.begin(); it != mat_index_map.end(); ++it) { - if (!BKE_object_material_slot_add(bmain, ob)) { - return; - } + if (mat_index_map.size() > MAXMAT) { + return; } std::map<std::string, Material *> matname_to_material = build_material_map(bmain); @@ -94,13 +94,17 @@ static void assign_materials(Main *bmain, mat_iter = matname_to_material.find(mat_name); if (mat_iter == matname_to_material.end()) { assigned_mat = BKE_material_add(bmain, mat_name.c_str()); + id_us_min(&assigned_mat->id); matname_to_material[mat_name] = assigned_mat; } else { assigned_mat = mat_iter->second; } - BKE_object_material_assign(bmain, ob, assigned_mat, mat_index, BKE_MAT_ASSIGN_OBDATA); + BKE_object_material_assign_single_obdata(bmain, ob, assigned_mat, mat_index); + } + if (ob->totcol > 0) { + ob->actcol = 1; } } @@ -153,8 +157,9 @@ static void read_mverts(CDStreamConfig &config, const AbcMeshData &mesh_data) void read_mverts(Mesh &mesh, const P3fArraySamplePtr positions, const N3fArraySamplePtr normals) { + MutableSpan<MVert> verts = mesh.verts_for_write(); for (int i = 0; i < positions->size(); i++) { - MVert &mvert = mesh.mvert[i]; + MVert &mvert = verts[i]; Imath::V3f pos_in = (*positions)[i]; copy_zup_from_yup(mvert.co, pos_in.getValue()); @@ -270,7 +275,7 @@ static void process_loop_normals(CDStreamConfig &config, const N3fArraySamplePtr float(*lnors)[3] = static_cast<float(*)[3]>( MEM_malloc_arrayN(loop_count, sizeof(float[3]), "ABC::FaceNormals")); - MPoly *mpoly = mesh->mpoly; + MPoly *mpoly = mesh->polys_for_write().data(); const N3fArraySample &loop_normals = *loop_normals_ptr; int abc_index = 0; for (int i = 0, e = mesh->totpoly; i < e; i++, mpoly++) { @@ -305,7 +310,7 @@ static void process_vertex_normals(CDStreamConfig &config, } config.mesh->flag |= ME_AUTOSMOOTH; - BKE_mesh_set_custom_normals_from_vertices(config.mesh, vnors); + BKE_mesh_set_custom_normals_from_verts(config.mesh, vnors); MEM_freeN(vnors); } @@ -392,7 +397,7 @@ static void *add_customdata_cb(Mesh *mesh, const char *name, int data_type) /* Create a new layer. */ int numloops = mesh->totloop; cd_ptr = CustomData_add_layer_named( - &mesh->ldata, cd_data_type, CD_DEFAULT, nullptr, numloops, name); + &mesh->ldata, cd_data_type, CD_SET_DEFAULT, nullptr, numloops, name); return cd_ptr; } @@ -515,13 +520,10 @@ static void read_mesh_sample(const std::string &iobject_full_name, CDStreamConfig get_config(Mesh *mesh, const bool use_vertex_interpolation) { CDStreamConfig config; - - BLI_assert(mesh->mvert || mesh->totvert == 0); - config.mesh = mesh; - config.mvert = mesh->mvert; - config.mloop = mesh->mloop; - config.mpoly = mesh->mpoly; + config.mvert = mesh->verts_for_write().data(); + config.mloop = mesh->loops_for_write().data(); + config.mpoly = mesh->polys_for_write().data(); config.totvert = mesh->totvert; config.totloop = mesh->totloop; config.totpoly = mesh->totpoly; @@ -767,7 +769,11 @@ Mesh *AbcMeshReader::read_mesh(Mesh *existing_mesh, size_t num_polys = new_mesh->totpoly; if (num_polys > 0) { std::map<std::string, int> mat_map; - assign_facesets_to_mpoly(sample_sel, new_mesh->mpoly, num_polys, mat_map); + bke::MutableAttributeAccessor attributes = new_mesh->attributes_for_write(); + bke::SpanAttributeWriter<int> material_indices = + attributes.lookup_or_add_for_write_only_span<int>("material_index", ATTR_DOMAIN_FACE); + assign_facesets_to_material_indices(sample_sel, material_indices.span, mat_map); + material_indices.finish(); } return new_mesh; @@ -776,10 +782,9 @@ Mesh *AbcMeshReader::read_mesh(Mesh *existing_mesh, return existing_mesh; } -void AbcMeshReader::assign_facesets_to_mpoly(const ISampleSelector &sample_sel, - MPoly *mpoly, - int totpoly, - std::map<std::string, int> &r_mat_map) +void AbcMeshReader::assign_facesets_to_material_indices(const ISampleSelector &sample_sel, + MutableSpan<int> material_indices, + std::map<std::string, int> &r_mat_map) { std::vector<std::string> face_sets; m_schema.getFaceSetNames(face_sets); @@ -812,13 +817,12 @@ void AbcMeshReader::assign_facesets_to_mpoly(const ISampleSelector &sample_sel, for (size_t l = 0; l < num_group_faces; l++) { size_t pos = (*group_faces)[l]; - if (pos >= totpoly) { + if (pos >= material_indices.size()) { std::cerr << "Faceset overflow on " << faceset.getName() << '\n'; break; } - MPoly &poly = mpoly[pos]; - poly.mat_nr = assigned_mat - 1; + material_indices[pos] = assigned_mat - 1; } } } @@ -826,25 +830,16 @@ void AbcMeshReader::assign_facesets_to_mpoly(const ISampleSelector &sample_sel, void AbcMeshReader::readFaceSetsSample(Main *bmain, Mesh *mesh, const ISampleSelector &sample_sel) { std::map<std::string, int> mat_map; - assign_facesets_to_mpoly(sample_sel, mesh->mpoly, mesh->totpoly, mat_map); + bke::MutableAttributeAccessor attributes = mesh->attributes_for_write(); + bke::SpanAttributeWriter<int> material_indices = + attributes.lookup_or_add_for_write_only_span<int>("material_index", ATTR_DOMAIN_FACE); + assign_facesets_to_material_indices(sample_sel, material_indices.span, mat_map); + material_indices.finish(); utils::assign_materials(bmain, m_object, mat_map); } /* ************************************************************************** */ -BLI_INLINE MEdge *find_edge(MEdge *edges, int totedge, int v1, int v2) -{ - for (int i = 0, e = totedge; i < e; i++) { - MEdge &edge = edges[i]; - - if (edge.v1 == v1 && edge.v2 == v2) { - return &edge; - } - } - - return nullptr; -} - static void read_subd_sample(const std::string &iobject_full_name, ImportSettings *settings, const ISubDSchema &schema, @@ -904,7 +899,7 @@ static void read_vertex_creases(Mesh *mesh, } float *vertex_crease_data = (float *)CustomData_add_layer( - &mesh->vdata, CD_CREASE, CD_DEFAULT, nullptr, mesh->totvert); + &mesh->vdata, CD_CREASE, CD_SET_DEFAULT, nullptr, mesh->totvert); const int totvert = mesh->totvert; for (int i = 0, v = indices->size(); i < v; ++i) { @@ -928,8 +923,13 @@ static void read_edge_creases(Mesh *mesh, return; } - MEdge *edges = mesh->medge; - int totedge = mesh->totedge; + MutableSpan<MEdge> edges = mesh->edges_for_write(); + EdgeHash *edge_hash = BLI_edgehash_new_ex(__func__, edges.size()); + + for (const int i : edges.index_range()) { + MEdge *edge = &edges[i]; + BLI_edgehash_insert(edge_hash, edge->v1, edge->v2, edge); + } for (int i = 0, s = 0, e = indices->size(); i < e; i += 2, s++) { int v1 = (*indices)[i]; @@ -941,9 +941,9 @@ static void read_edge_creases(Mesh *mesh, std::swap(v1, v2); } - MEdge *edge = find_edge(edges, totedge, v1, v2); + MEdge *edge = static_cast<MEdge *>(BLI_edgehash_lookup(edge_hash, v1, v2)); if (edge == nullptr) { - edge = find_edge(edges, totedge, v2, v1); + edge = static_cast<MEdge *>(BLI_edgehash_lookup(edge_hash, v2, v1)); } if (edge) { @@ -951,6 +951,8 @@ static void read_edge_creases(Mesh *mesh, } } + BLI_edgehash_free(edge_hash, nullptr); + mesh->cd_flag |= ME_CDFLAG_EDGE_CREASE; } diff --git a/source/blender/io/alembic/intern/abc_reader_mesh.h b/source/blender/io/alembic/intern/abc_reader_mesh.h index f97525297b7..151f4d82226 100644 --- a/source/blender/io/alembic/intern/abc_reader_mesh.h +++ b/source/blender/io/alembic/intern/abc_reader_mesh.h @@ -5,6 +5,8 @@ * \ingroup balembic */ +#include "BLI_span.hh" + #include "abc_customdata.h" #include "abc_reader_object.h" @@ -38,10 +40,9 @@ class AbcMeshReader final : public AbcObjectReader { Mesh *mesh, const Alembic::AbcGeom::ISampleSelector &sample_sel); - void assign_facesets_to_mpoly(const Alembic::Abc::ISampleSelector &sample_sel, - MPoly *mpoly, - int totpoly, - std::map<std::string, int> &r_mat_map); + void assign_facesets_to_material_indices(const Alembic::Abc::ISampleSelector &sample_sel, + MutableSpan<int> material_indices, + std::map<std::string, int> &r_mat_map); }; class AbcSubDReader final : public AbcObjectReader { diff --git a/source/blender/io/alembic/intern/abc_reader_object.cc b/source/blender/io/alembic/intern/abc_reader_object.cc index a698eeca8f1..db056c0eef6 100644 --- a/source/blender/io/alembic/intern/abc_reader_object.cc +++ b/source/blender/io/alembic/intern/abc_reader_object.cc @@ -218,12 +218,12 @@ Alembic::AbcGeom::IXform AbcObjectReader::xform() void AbcObjectReader::read_matrix(float r_mat[4][4] /* local matrix */, const chrono_t time, const float scale, - bool &is_constant) + bool &r_is_constant) { IXform ixform = xform(); if (!ixform) { unit_m4(r_mat); - is_constant = true; + r_is_constant = true; return; } @@ -254,7 +254,7 @@ void AbcObjectReader::read_matrix(float r_mat[4][4] /* local matrix */, mul_m4_m4m4(r_mat, scale_mat, r_mat); } - is_constant = schema.isConstant(); + r_is_constant = schema.isConstant(); } void AbcObjectReader::addCacheModifier() diff --git a/source/blender/io/alembic/intern/alembic_capi.cc b/source/blender/io/alembic/intern/alembic_capi.cc index 0d4e1d04db0..86622719f6e 100644 --- a/source/blender/io/alembic/intern/alembic_capi.cc +++ b/source/blender/io/alembic/intern/alembic_capi.cc @@ -50,6 +50,7 @@ #include "BLI_math.h" #include "BLI_path_util.h" #include "BLI_string.h" +#include "BLI_timeit.hh" #include "WM_api.h" #include "WM_types.h" @@ -434,8 +435,17 @@ struct ImportJobData { bool was_cancelled; bool import_ok; bool is_background_job; + blender::timeit::TimePoint start_time; }; +static void report_job_duration(const ImportJobData *data) +{ + blender::timeit::Nanoseconds duration = blender::timeit::Clock::now() - data->start_time; + std::cout << "Alembic import of '" << data->filename << "' took "; + blender::timeit::print_duration(duration); + std::cout << '\n'; +} + static void import_startjob(void *user_data, short *stop, short *do_update, float *progress) { SCOPE_TIMER("Alembic import, objects reading and creation"); @@ -445,6 +455,7 @@ static void import_startjob(void *user_data, short *stop, short *do_update, floa data->stop = stop; data->do_update = do_update; data->progress = progress; + data->start_time = blender::timeit::Clock::now(); WM_set_locked_interface(data->wm, true); @@ -526,14 +537,14 @@ static void import_startjob(void *user_data, short *stop, short *do_update, floa Scene *scene = data->scene; if (data->settings.is_sequence) { - SFRA = data->settings.sequence_offset; - EFRA = SFRA + (data->settings.sequence_len - 1); - CFRA = SFRA; + scene->r.sfra = data->settings.sequence_offset; + scene->r.efra = scene->r.sfra + (data->settings.sequence_len - 1); + scene->r.cfra = scene->r.sfra; } else if (min_time < max_time) { - SFRA = static_cast<int>(round(min_time * FPS)); - EFRA = static_cast<int>(round(max_time * FPS)); - CFRA = SFRA; + scene->r.sfra = static_cast<int>(round(min_time * FPS)); + scene->r.efra = static_cast<int>(round(max_time * FPS)); + scene->r.cfra = scene->r.sfra; } } @@ -573,12 +584,10 @@ static void import_endjob(void *user_data) ImportJobData *data = static_cast<ImportJobData *>(user_data); - std::vector<AbcObjectReader *>::iterator iter; - /* Delete objects on cancellation. */ if (data->was_cancelled) { - for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) { - Object *ob = (*iter)->object(); + for (AbcObjectReader *reader : data->readers) { + Object *ob = reader->object(); /* It's possible that cancellation occurred between the creation of * the reader and the creation of the Blender object. */ @@ -590,7 +599,6 @@ static void import_endjob(void *user_data) } } else { - /* Add object to scene. */ Base *base; LayerCollection *lc; ViewLayer *view_layer = data->view_layer; @@ -599,11 +607,17 @@ static void import_endjob(void *user_data) lc = BKE_layer_collection_get_active(view_layer); - for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) { - Object *ob = (*iter)->object(); - + /* Add all objects to the collection (don't do sync for each object). */ + BKE_layer_collection_resync_forbid(); + for (AbcObjectReader *reader : data->readers) { + Object *ob = reader->object(); BKE_collection_object_add(data->bmain, lc->collection, ob); - + } + /* Sync the collection, and do view layer operations. */ + BKE_layer_collection_resync_allow(); + BKE_main_collection_sync(data->bmain); + for (AbcObjectReader *reader : data->readers) { + Object *ob = reader->object(); base = BKE_view_layer_base_find(view_layer, ob); /* TODO: is setting active needed? */ BKE_view_layer_base_select_and_set_active(view_layer, base); @@ -625,8 +639,7 @@ static void import_endjob(void *user_data) } } - for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) { - AbcObjectReader *reader = *iter; + for (AbcObjectReader *reader : data->readers) { reader->decref(); if (reader->refcount() == 0) { @@ -647,6 +660,7 @@ static void import_endjob(void *user_data) } WM_main_add_notifier(NC_SCENE | ND_FRAME, data->scene); + report_job_duration(data); } static void import_freejob(void *user_data) @@ -658,13 +672,7 @@ static void import_freejob(void *user_data) bool ABC_import(bContext *C, const char *filepath, - float scale, - bool is_sequence, - bool set_frame_range, - int sequence_len, - int offset, - bool validate_meshes, - bool always_add_cache_reader, + const AlembicImportParams *params, bool as_background_job) { /* Using new here since MEM_* functions do not call constructor to properly initialize data. */ @@ -677,13 +685,13 @@ bool ABC_import(bContext *C, job->import_ok = false; BLI_strncpy(job->filename, filepath, 1024); - job->settings.scale = scale; - job->settings.is_sequence = is_sequence; - job->settings.set_frame_range = set_frame_range; - job->settings.sequence_len = sequence_len; - job->settings.sequence_offset = offset; - job->settings.validate_meshes = validate_meshes; - job->settings.always_add_cache_reader = always_add_cache_reader; + job->settings.scale = params->global_scale; + job->settings.is_sequence = params->is_sequence; + job->settings.set_frame_range = params->set_frame_range; + job->settings.sequence_len = params->sequence_len; + job->settings.sequence_offset = params->sequence_offset; + job->settings.validate_meshes = params->validate_meshes; + job->settings.always_add_cache_reader = params->always_add_cache_reader; job->error_code = ABC_NO_ERROR; job->was_cancelled = false; job->archive = nullptr; diff --git a/source/blender/io/collada/AnimationImporter.cpp b/source/blender/io/collada/AnimationImporter.cpp index 923a392dbde..cc91c3eeac9 100644 --- a/source/blender/io/collada/AnimationImporter.cpp +++ b/source/blender/io/collada/AnimationImporter.cpp @@ -64,7 +64,7 @@ void AnimationImporter::add_bezt(FCurve *fcu, bez.f1 = bez.f2 = bez.f3 = SELECT; bez.h1 = bez.h2 = HD_AUTO; insert_bezt_fcurve(fcu, &bez, INSERTKEY_NOFLAGS); - calchandles_fcurve(fcu); + BKE_fcurve_handles_recalc(fcu); } void AnimationImporter::animation_to_fcurves(COLLADAFW::AnimationCurve *curve) @@ -132,7 +132,7 @@ void AnimationImporter::animation_to_fcurves(COLLADAFW::AnimationCurve *curve) insert_bezt_fcurve(fcu, &bez, INSERTKEY_NOFLAGS); } - calchandles_fcurve(fcu); + BKE_fcurve_handles_recalc(fcu); fcurves.push_back(fcu); unused_curves.push_back(fcu); diff --git a/source/blender/io/collada/ArmatureExporter.cpp b/source/blender/io/collada/ArmatureExporter.cpp index 87dd2fbd816..3cc98917116 100644 --- a/source/blender/io/collada/ArmatureExporter.cpp +++ b/source/blender/io/collada/ArmatureExporter.cpp @@ -76,7 +76,7 @@ bool ArmatureExporter::add_instance_controller(Object *ob) ins.setUrl(COLLADASW::URI(COLLADABU::Utils::EMPTY_STRING, controller_id)); Mesh *me = (Mesh *)ob->data; - if (!me->dvert) { + if (BKE_mesh_deform_verts(me) == nullptr) { return false; } diff --git a/source/blender/io/collada/BCAnimationCurve.cpp b/source/blender/io/collada/BCAnimationCurve.cpp index fbb2ba499a5..fe90dc5d5fa 100644 --- a/source/blender/io/collada/BCAnimationCurve.cpp +++ b/source/blender/io/collada/BCAnimationCurve.cpp @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: GPL-2.0-or-later * Copyright 2008 Blender Foundation. All rights reserved. */ +#include "RNA_path.h" + #include "BCAnimationCurve.h" BCAnimationCurve::BCAnimationCurve() @@ -96,7 +98,7 @@ void BCAnimationCurve::create_bezt(float frame, float output) bez.f1 = bez.f2 = bez.f3 = SELECT; bez.h1 = bez.h2 = HD_AUTO; insert_bezt_fcurve(fcu, &bez, INSERTKEY_NOFLAGS); - calchandles_fcurve(fcu); + BKE_fcurve_handles_recalc(fcu); } BCAnimationCurve::~BCAnimationCurve() diff --git a/source/blender/io/collada/ControllerExporter.cpp b/source/blender/io/collada/ControllerExporter.cpp index 38ad0e42d0f..6bf8d904a41 100644 --- a/source/blender/io/collada/ControllerExporter.cpp +++ b/source/blender/io/collada/ControllerExporter.cpp @@ -63,7 +63,7 @@ bool ControllerExporter::add_instance_controller(Object *ob) ins.setUrl(COLLADASW::URI(COLLADABU::Utils::EMPTY_STRING, controller_id)); Mesh *me = (Mesh *)ob->data; - if (!me->dvert) { + if (BKE_mesh_deform_verts(me) == nullptr) { return false; } @@ -160,7 +160,7 @@ void ControllerExporter::export_skin_controller(Object *ob, Object *ob_arm) bool use_instantiation = this->export_settings.get_use_object_instantiation(); Mesh *me; - if (((Mesh *)ob->data)->dvert == nullptr) { + if (BKE_mesh_deform_verts((Mesh *)ob->data) == nullptr) { return; } @@ -203,9 +203,10 @@ void ControllerExporter::export_skin_controller(Object *ob, Object *ob_arm) } } + const MDeformVert *dvert = BKE_mesh_deform_verts(me); int oob_counter = 0; for (i = 0; i < me->totvert; i++) { - MDeformVert *vert = &me->dvert[i]; + const MDeformVert *vert = &dvert[i]; std::map<int, float> jw; /* We're normalizing the weights later */ diff --git a/source/blender/io/collada/DocumentImporter.cpp b/source/blender/io/collada/DocumentImporter.cpp index 2ce97bc8b5d..1ffe412b3ed 100644 --- a/source/blender/io/collada/DocumentImporter.cpp +++ b/source/blender/io/collada/DocumentImporter.cpp @@ -743,6 +743,7 @@ bool DocumentImporter::writeMaterial(const COLLADAFW::Material *cmat) const std::string &str_mat_id = cmat->getName().empty() ? cmat->getOriginalId() : cmat->getName(); Material *ma = BKE_material_add(bmain, (char *)str_mat_id.c_str()); + id_us_min(&ma->id); this->uid_effect_map[cmat->getInstantiatedEffect()] = ma; this->uid_material_map[cmat->getUniqueId()] = ma; diff --git a/source/blender/io/collada/EffectExporter.cpp b/source/blender/io/collada/EffectExporter.cpp index 71a54e3a7c9..40ce20617fc 100644 --- a/source/blender/io/collada/EffectExporter.cpp +++ b/source/blender/io/collada/EffectExporter.cpp @@ -46,6 +46,7 @@ EffectsExporter::EffectsExporter(COLLADASW::StreamWriter *sw, bool EffectsExporter::hasEffects(Scene *sce) { + bool result = false; FOREACH_SCENE_OBJECT_BEGIN (sce, ob) { int a; for (a = 0; a < ob->totcol; a++) { @@ -56,11 +57,12 @@ bool EffectsExporter::hasEffects(Scene *sce) continue; } - return true; + result = true; + break; } } FOREACH_SCENE_OBJECT_END; - return false; + return result; } void EffectsExporter::exportEffects(bContext *C, Scene *sce) diff --git a/source/blender/io/collada/GeometryExporter.cpp b/source/blender/io/collada/GeometryExporter.cpp index 7e2a24aeb41..e60900ccdb6 100644 --- a/source/blender/io/collada/GeometryExporter.cpp +++ b/source/blender/io/collada/GeometryExporter.cpp @@ -17,6 +17,7 @@ #include "BLI_utildefines.h" +#include "BKE_attribute.hh" #include "BKE_customdata.h" #include "BKE_global.h" #include "BKE_lib_id.h" @@ -26,6 +27,8 @@ #include "collada_internal.h" #include "collada_utils.h" +using blender::Span; + void GeometryExporter::exportGeom() { Scene *sce = blender_context.get_scene(); @@ -116,11 +119,12 @@ void GeometryExporter::operator()(Object *ob) if (this->export_settings.get_include_shapekeys()) { Key *key = BKE_key_from_object(ob); if (key) { + blender::MutableSpan<MVert> verts = me->verts_for_write(); KeyBlock *kb = (KeyBlock *)key->block.first; /* skip the basis */ kb = kb->next; for (; kb; kb = kb->next) { - BKE_keyblock_convert_to_mesh(kb, me->mvert, me->totvert); + BKE_keyblock_convert_to_mesh(kb, verts.data(), me->totvert); export_key_mesh(ob, me, kb); } } @@ -196,8 +200,7 @@ void GeometryExporter::export_key_mesh(Object *ob, Mesh *me, KeyBlock *kb) void GeometryExporter::createLooseEdgeList(Object *ob, Mesh *me, std::string &geom_id) { - - MEdge *medges = me->medge; + const Span<MEdge> edges = me->edges(); int totedges = me->totedge; int edges_in_linelist = 0; std::vector<unsigned int> edge_list; @@ -206,7 +209,7 @@ void GeometryExporter::createLooseEdgeList(Object *ob, Mesh *me, std::string &ge /* Find all loose edges in Mesh * and save vertex indices in edge_list */ for (index = 0; index < totedges; index++) { - MEdge *edge = &medges[index]; + const MEdge *edge = &edges[index]; if (edge->flag & ME_LOOSEEDGE) { edges_in_linelist += 1; @@ -284,16 +287,17 @@ static bool collect_vertex_counts_per_poly(Mesh *me, int material_index, std::vector<unsigned long> &vcount_list) { - MPoly *mpolys = me->mpoly; - int totpolys = me->totpoly; + const Span<MPoly> polys = me->polys(); + const blender::bke::AttributeAccessor attributes = me->attributes(); + const blender::VArray<int> material_indices = attributes.lookup_or_default<int>( + "material_index", ATTR_DOMAIN_FACE, 0); bool is_triangulated = true; - int i; - /* Expecting that p->mat_nr is always 0 if the mesh has no materials assigned */ - for (i = 0; i < totpolys; i++) { - MPoly *p = &mpolys[i]; - if (p->mat_nr == material_index) { - int vertex_count = p->totloop; + /* Expecting that the material index is always 0 if the mesh has no materials assigned */ + for (const int i : polys.index_range()) { + if (material_indices[i] == material_index) { + const MPoly &poly = polys[i]; + const int vertex_count = poly.totloop; vcount_list.push_back(vertex_count); if (vertex_count != 3) { is_triangulated = false; @@ -318,10 +322,8 @@ void GeometryExporter::create_mesh_primitive_list(short material_index, std::string &geom_id, std::vector<BCPolygonNormalsIndices> &norind) { - - MPoly *mpolys = me->mpoly; - MLoop *mloops = me->mloop; - int totpolys = me->totpoly; + const Span<MPoly> polys = me->polys(); + const Span<MLoop> loops = me->loops(); std::vector<unsigned long> vcount_list; @@ -397,14 +399,18 @@ void GeometryExporter::create_mesh_primitive_list(short material_index, /* performs the actual writing */ prepareToAppendValues(is_triangulated, *primitive_list, vcount_list); + const blender::bke::AttributeAccessor attributes = me->attributes(); + const blender::VArray<int> material_indices = attributes.lookup_or_default<int>( + "material_index", ATTR_DOMAIN_FACE, 0); + /* <p> */ int texindex = 0; - for (int i = 0; i < totpolys; i++) { - MPoly *p = &mpolys[i]; + for (const int i : polys.index_range()) { + const MPoly *p = &polys[i]; int loop_count = p->totloop; - if (p->mat_nr == material_index) { - MLoop *l = &mloops[p->loopstart]; + if (material_indices[i] == material_index) { + const MLoop *l = &loops[p->loopstart]; BCPolygonNormalsIndices normal_indices = norind[i]; for (int j = 0; j < loop_count; j++) { @@ -428,18 +434,13 @@ void GeometryExporter::create_mesh_primitive_list(short material_index, void GeometryExporter::createVertsSource(std::string geom_id, Mesh *me) { -#if 0 - int totverts = dm->getNumVerts(dm); - MVert *verts = dm->getVertArray(dm); -#endif - int totverts = me->totvert; - MVert *verts = me->mvert; + const Span<MVert> verts = me->verts(); COLLADASW::FloatSourceF source(mSW); source.setId(getIdBySemantics(geom_id, COLLADASW::InputSemantic::POSITION)); source.setArrayId(getIdBySemantics(geom_id, COLLADASW::InputSemantic::POSITION) + ARRAY_ID_SUFFIX); - source.setAccessorCount(totverts); + source.setAccessorCount(verts.size()); source.setAccessorStride(3); COLLADASW::SourceBase::ParameterNameList ¶m = source.getParameterNameList(); @@ -450,8 +451,7 @@ void GeometryExporter::createVertsSource(std::string geom_id, Mesh *me) * count = ""> */ source.prepareToAppendValues(); /* appends data to <float_array> */ - int i = 0; - for (i = 0; i < totverts; i++) { + for (const int i : verts.index_range()) { Vector co; if (export_settings.get_apply_global_orientation()) { bc_add_global_transform(co, verts[i].co, export_settings.get_global_transform()); @@ -500,11 +500,11 @@ void GeometryExporter::createVertexColorSource(std::string geom_id, Mesh *me) source.prepareToAppendValues(); - MPoly *mpoly; - int i; - for (i = 0, mpoly = me->mpoly; i < me->totpoly; i++, mpoly++) { - const MLoopCol *mlc = mloopcol + mpoly->loopstart; - for (int j = 0; j < mpoly->totloop; j++, mlc++) { + const Span<MPoly> polys = me->polys(); + for (const int i : polys.index_range()) { + const MPoly &poly = polys[i]; + const MLoopCol *mlc = mloopcol + poly.loopstart; + for (int j = 0; j < poly.totloop; j++, mlc++) { source.appendValues(mlc->r / 255.0f, mlc->g / 255.0f, mlc->b / 255.0f, mlc->a / 255.0f); } } @@ -529,10 +529,8 @@ std::string GeometryExporter::makeTexcoordSourceId(std::string &geom_id, void GeometryExporter::createTexcoordsSource(std::string geom_id, Mesh *me) { - - int totpoly = me->totpoly; int totuv = me->totloop; - MPoly *mpolys = me->mpoly; + const Span<MPoly> polys = me->polys(); int num_layers = CustomData_number_of_layers(&me->ldata, CD_MLOOPUV); @@ -558,8 +556,8 @@ void GeometryExporter::createTexcoordsSource(std::string geom_id, Mesh *me) source.prepareToAppendValues(); - for (int index = 0; index < totpoly; index++) { - MPoly *mpoly = mpolys + index; + for (const int i : polys.index_range()) { + const MPoly *mpoly = &polys[i]; MLoopUV *mloop = mloops + mpoly->loopstart; for (int j = 0; j < mpoly->totloop; j++) { source.appendValues(mloop[j].uv[0], mloop[j].uv[1]); @@ -617,9 +615,10 @@ void GeometryExporter::create_normals(std::vector<Normal> &normals, std::map<Normal, unsigned int> shared_normal_indices; int last_normal_index = -1; - MVert *verts = me->mvert; + const Span<MVert> verts = me->verts(); const float(*vert_normals)[3] = BKE_mesh_vertex_normals_ensure(me); - MLoop *mloops = me->mloop; + const Span<MPoly> polys = me->polys(); + const Span<MLoop> loops = me->loops(); const float(*lnors)[3] = nullptr; bool use_custom_normals = false; @@ -629,15 +628,15 @@ void GeometryExporter::create_normals(std::vector<Normal> &normals, use_custom_normals = true; } - for (int poly_index = 0; poly_index < me->totpoly; poly_index++) { - MPoly *mpoly = &me->mpoly[poly_index]; + for (const int poly_index : polys.index_range()) { + const MPoly *mpoly = &polys[poly_index]; bool use_vertex_normals = use_custom_normals || mpoly->flag & ME_SMOOTH; if (!use_vertex_normals) { /* For flat faces use face normal as vertex normal: */ float vector[3]; - BKE_mesh_calc_poly_normal(mpoly, mloops + mpoly->loopstart, verts, vector); + BKE_mesh_calc_poly_normal(mpoly, &loops[mpoly->loopstart], verts.data(), vector); Normal n = {vector[0], vector[1], vector[2]}; normals.push_back(n); @@ -654,7 +653,7 @@ void GeometryExporter::create_normals(std::vector<Normal> &normals, normalize_v3_v3(normalized, lnors[loop_idx]); } else { - copy_v3_v3(normalized, vert_normals[mloops[loop_index].v]); + copy_v3_v3(normalized, vert_normals[loops[loop_index].v]); normalize_v3(normalized); } Normal n = {normalized[0], normalized[1], normalized[2]}; diff --git a/source/blender/io/collada/Materials.cpp b/source/blender/io/collada/Materials.cpp index b5d89d8d1cf..997da31b939 100644 --- a/source/blender/io/collada/Materials.cpp +++ b/source/blender/io/collada/Materials.cpp @@ -86,7 +86,7 @@ bNodeTree *MaterialNode::prepare_material_nodetree() return nullptr; } - material->nodetree = ntreeAddTree(nullptr, "Shader Nodetree", "ShaderNodeTree"); + ntreeAddTreeEmbedded(nullptr, &material->id, "Shader Nodetree", "ShaderNodeTree"); material->use_nodes = true; ntree = material->nodetree; return ntree; diff --git a/source/blender/io/collada/MeshImporter.cpp b/source/blender/io/collada/MeshImporter.cpp index 6e109353be8..e7a4f7b6b51 100644 --- a/source/blender/io/collada/MeshImporter.cpp +++ b/source/blender/io/collada/MeshImporter.cpp @@ -33,6 +33,8 @@ #include "MeshImporter.h" #include "collada_utils.h" +using blender::MutableSpan; + /* get node name, or fall back to original id if not present (name is optional) */ template<class T> static std::string bc_get_dae_name(T *node) { @@ -145,6 +147,27 @@ VCOLDataWrapper::VCOLDataWrapper(COLLADAFW::MeshVertexData &vdata) : mVData(&vda { } +template<typename T> +static void colladaAddColor(T values, MLoopCol *mloopcol, int v_index, int stride) +{ + if (values->empty() || values->getCount() < (v_index + 1) * stride) { + fprintf(stderr, + "VCOLDataWrapper.getvcol(): Out of Bounds error: index %d points outside value " + "list of length %zd (with stride=%d) \n", + v_index, + values->getCount(), + stride); + return; + } + + mloopcol->r = unit_float_to_uchar_clamp((*values)[v_index * stride]); + mloopcol->g = unit_float_to_uchar_clamp((*values)[v_index * stride + 1]); + mloopcol->b = unit_float_to_uchar_clamp((*values)[v_index * stride + 2]); + if (stride == 4) { + mloopcol->a = unit_float_to_uchar_clamp((*values)[v_index * stride + 3]); + } +} + void VCOLDataWrapper::get_vcol(int v_index, MLoopCol *mloopcol) { int stride = mVData->getStride(0); @@ -155,25 +178,14 @@ void VCOLDataWrapper::get_vcol(int v_index, MLoopCol *mloopcol) switch (mVData->getType()) { case COLLADAFW::MeshVertexData::DATA_TYPE_FLOAT: { COLLADAFW::ArrayPrimitiveType<float> *values = mVData->getFloatValues(); - if (values->empty() || values->getCount() <= (v_index * stride + 2)) { - return; /* XXX: need to create an error instead. */ - } - - mloopcol->r = unit_float_to_uchar_clamp((*values)[v_index * stride]); - mloopcol->g = unit_float_to_uchar_clamp((*values)[v_index * stride + 1]); - mloopcol->b = unit_float_to_uchar_clamp((*values)[v_index * stride + 2]); + colladaAddColor<COLLADAFW::ArrayPrimitiveType<float> *>(values, mloopcol, v_index, stride); } break; case COLLADAFW::MeshVertexData::DATA_TYPE_DOUBLE: { COLLADAFW::ArrayPrimitiveType<double> *values = mVData->getDoubleValues(); - if (values->empty() || values->getCount() <= (v_index * stride + 2)) { - return; /* XXX: need to create an error instead. */ - } - - mloopcol->r = unit_float_to_uchar_clamp((*values)[v_index * stride]); - mloopcol->g = unit_float_to_uchar_clamp((*values)[v_index * stride + 1]); - mloopcol->b = unit_float_to_uchar_clamp((*values)[v_index * stride + 2]); + colladaAddColor<COLLADAFW::ArrayPrimitiveType<double> *>(values, mloopcol, v_index, stride); } break; + default: fprintf(stderr, "VCOLDataWrapper.getvcol(): unknown data type\n"); } @@ -331,13 +343,10 @@ void MeshImporter::read_vertices(COLLADAFW::Mesh *mesh, Mesh *me) } me->totvert = pos.getFloatValues()->getCount() / stride; - me->mvert = (MVert *)CustomData_add_layer(&me->vdata, CD_MVERT, CD_CALLOC, nullptr, me->totvert); - - MVert *mvert; - int i; - - for (i = 0, mvert = me->mvert; i < me->totvert; i++, mvert++) { - get_vector(mvert->co, pos, i, stride); + CustomData_add_layer(&me->vdata, CD_MVERT, CD_SET_DEFAULT, nullptr, me->totvert); + MutableSpan<MVert> verts = me->verts_for_write(); + for (const int i : verts.index_range()) { + get_vector(verts[i].co, pos, i, stride); } } @@ -438,10 +447,8 @@ void MeshImporter::allocate_poly_data(COLLADAFW::Mesh *collada_mesh, Mesh *me) if (total_poly_count > 0) { me->totpoly = total_poly_count; me->totloop = total_loop_count; - me->mpoly = (MPoly *)CustomData_add_layer( - &me->pdata, CD_MPOLY, CD_CALLOC, nullptr, me->totpoly); - me->mloop = (MLoop *)CustomData_add_layer( - &me->ldata, CD_MLOOP, CD_CALLOC, nullptr, me->totloop); + CustomData_add_layer(&me->pdata, CD_MPOLY, CD_SET_DEFAULT, nullptr, me->totpoly); + CustomData_add_layer(&me->ldata, CD_MLOOP, CD_SET_DEFAULT, nullptr, me->totloop); unsigned int totuvset = collada_mesh->getUVCoords().getInputInfosArray().getCount(); for (int i = 0; i < totuvset; i++) { @@ -458,10 +465,10 @@ void MeshImporter::allocate_poly_data(COLLADAFW::Mesh *collada_mesh, Mesh *me) COLLADAFW::String &uvname = info->mName; /* Allocate space for UV_data */ CustomData_add_layer_named( - &me->ldata, CD_MLOOPUV, CD_DEFAULT, nullptr, me->totloop, uvname.c_str()); + &me->ldata, CD_MLOOPUV, CD_SET_DEFAULT, nullptr, me->totloop, uvname.c_str()); } /* activate the first uv map */ - me->mloopuv = (MLoopUV *)CustomData_get_layer_n(&me->ldata, CD_MLOOPUV, 0); + CustomData_set_layer_active(&me->ldata, CD_MLOOPUV, 0); } int totcolset = collada_mesh->getColors().getInputInfosArray().getCount(); @@ -471,9 +478,9 @@ void MeshImporter::allocate_poly_data(COLLADAFW::Mesh *collada_mesh, Mesh *me) collada_mesh->getColors().getInputInfosArray()[i]; COLLADAFW::String colname = extract_vcolname(info->mName); CustomData_add_layer_named( - &me->ldata, CD_PROP_BYTE_COLOR, CD_DEFAULT, nullptr, me->totloop, colname.c_str()); + &me->ldata, CD_PROP_BYTE_COLOR, CD_SET_DEFAULT, nullptr, me->totloop, colname.c_str()); } - me->mloopcol = (MLoopCol *)CustomData_get_layer_n(&me->ldata, CD_PROP_BYTE_COLOR, 0); + CustomData_set_layer_active(&me->ldata, CD_PROP_BYTE_COLOR, 0); } } } @@ -536,19 +543,20 @@ void MeshImporter::mesh_add_edges(Mesh *mesh, int len) totedge = mesh->totedge + len; /* Update custom-data. */ - CustomData_copy(&mesh->edata, &edata, CD_MASK_MESH.emask, CD_DEFAULT, totedge); + CustomData_copy(&mesh->edata, &edata, CD_MASK_MESH.emask, CD_SET_DEFAULT, totedge); CustomData_copy_data(&mesh->edata, &edata, 0, 0, mesh->totedge); if (!CustomData_has_layer(&edata, CD_MEDGE)) { - CustomData_add_layer(&edata, CD_MEDGE, CD_CALLOC, nullptr, totedge); + CustomData_add_layer(&edata, CD_MEDGE, CD_SET_DEFAULT, nullptr, totedge); } CustomData_free(&mesh->edata, mesh->totedge); mesh->edata = edata; - BKE_mesh_update_customdata_pointers(mesh, false); /* new edges don't change tessellation */ + + MutableSpan<MEdge> edges = mesh->edges_for_write(); /* set default flags */ - medge = &mesh->medge[mesh->totedge]; + medge = &edges[mesh->totedge]; for (int i = 0; i < len; i++, medge++) { medge->flag = ME_EDGEDRAW | ME_EDGERENDER | SELECT; } @@ -565,7 +573,8 @@ void MeshImporter::read_lines(COLLADAFW::Mesh *mesh, Mesh *me) /* unsigned int total_edge_count = loose_edge_count + face_edge_count; */ /* UNUSED */ mesh_add_edges(me, loose_edge_count); - MEdge *med = me->medge + face_edge_count; + MutableSpan<MEdge> edges = me->edges_for_write(); + MEdge *med = edges.data() + face_edge_count; COLLADAFW::MeshPrimitiveArray &prim_arr = mesh->getMeshPrimitives(); @@ -598,12 +607,17 @@ void MeshImporter::read_polys(COLLADAFW::Mesh *collada_mesh, Mesh *me) UVDataWrapper uvs(collada_mesh->getUVCoords()); VCOLDataWrapper vcol(collada_mesh->getColors()); - MPoly *mpoly = me->mpoly; - MLoop *mloop = me->mloop; + MutableSpan<MPoly> polys = me->polys_for_write(); + MutableSpan<MLoop> loops = me->loops_for_write(); + MPoly *mpoly = polys.data(); + MLoop *mloop = loops.data(); int loop_index = 0; MaterialIdPrimitiveArrayMap mat_prim_map; + int *material_indices = (int *)CustomData_add_layer_named( + &me->pdata, CD_PROP_INT32, CD_SET_DEFAULT, nullptr, me->totpoly, "material_index"); + COLLADAFW::MeshPrimitiveArray &prim_arr = collada_mesh->getMeshPrimitives(); COLLADAFW::MeshVertexData &nor = collada_mesh->getNormals(); @@ -622,7 +636,7 @@ void MeshImporter::read_polys(COLLADAFW::Mesh *collada_mesh, Mesh *me) int collada_meshtype = mp->getPrimitiveType(); /* since we cannot set mpoly->mat_nr here, we store a portion of me->mpoly in Primitive */ - Primitive prim = {mpoly, 0}; + Primitive prim = {mpoly, material_indices, 0}; /* If MeshPrimitive is TRIANGLE_FANS we split it into triangles * The first triangle-fan vertex will be the first vertex in every triangle @@ -652,6 +666,9 @@ void MeshImporter::read_polys(COLLADAFW::Mesh *collada_mesh, Mesh *me) } mpoly++; + if (material_indices) { + material_indices++; + } mloop += 3; loop_index += 3; prim.totpoly++; @@ -887,11 +904,9 @@ static bool bc_has_same_material_configuration(Object *ob1, Object *ob2) } /** - * * Caution here: This code assumes that all materials are assigned to Object * and no material is assigned to Data. * That is true right after the objects have been imported. - * */ static void bc_copy_materials_to_data(Object *ob, Mesh *me) { @@ -902,9 +917,7 @@ static void bc_copy_materials_to_data(Object *ob, Mesh *me) } /** - * - * Remove all references to materials from the object - * + * Remove all references to materials from the object. */ static void bc_remove_materials_from_object(Object *ob, Mesh *me) { @@ -1000,10 +1013,9 @@ void MeshImporter::assign_material_to_geom( for (it = prims.begin(); it != prims.end(); it++) { Primitive &prim = *it; - MPoly *mpoly = prim.mpoly; - for (int i = 0; i < prim.totpoly; i++, mpoly++) { - mpoly->mat_nr = mat_index; + for (int i = 0; i < prim.totpoly; i++) { + prim.material_indices[i] = mat_index; } } } diff --git a/source/blender/io/collada/MeshImporter.h b/source/blender/io/collada/MeshImporter.h index 416b5728b66..1def84e8f99 100644 --- a/source/blender/io/collada/MeshImporter.h +++ b/source/blender/io/collada/MeshImporter.h @@ -80,6 +80,7 @@ class MeshImporter : public MeshImporterBase { * (<triangles>, <polylist>, etc.) */ struct Primitive { MPoly *mpoly; + int *material_indices; unsigned int totpoly; }; typedef std::map<COLLADAFW::MaterialId, std::vector<Primitive>> MaterialIdPrimitiveArrayMap; @@ -203,7 +204,6 @@ class MeshImporter : public MeshImporterBase { * if the check is positive: * Add the materials of the first user to the geometry * adjust all other users accordingly. - * */ void optimize_material_assignements(); diff --git a/source/blender/io/collada/collada_utils.cpp b/source/blender/io/collada/collada_utils.cpp index 0c902700b6b..82c471a6524 100644 --- a/source/blender/io/collada/collada_utils.cpp +++ b/source/blender/io/collada/collada_utils.cpp @@ -40,6 +40,7 @@ #include "BKE_lib_id.h" #include "BKE_material.h" #include "BKE_mesh.h" +#include "BKE_mesh_legacy_convert.h" #include "BKE_mesh_runtime.h" #include "BKE_node.h" #include "BKE_object.h" @@ -1107,7 +1108,7 @@ static std::string bc_get_uvlayer_name(Mesh *me, int layer) static bNodeTree *prepare_material_nodetree(Material *ma) { if (ma->nodetree == nullptr) { - ma->nodetree = ntreeAddTree(nullptr, "Shader Nodetree", "ShaderNodeTree"); + ntreeAddTreeEmbedded(nullptr, &ma->id, "Shader Nodetree", "ShaderNodeTree"); ma->use_nodes = true; } return ma->nodetree; diff --git a/source/blender/io/common/CMakeLists.txt b/source/blender/io/common/CMakeLists.txt index a6818c0bf5d..ee5c6a0a47f 100644 --- a/source/blender/io/common/CMakeLists.txt +++ b/source/blender/io/common/CMakeLists.txt @@ -19,15 +19,15 @@ set(SRC intern/dupli_parent_finder.cc intern/dupli_persistent_id.cc intern/object_identifier.cc - intern/path_util.cc intern/orientation.c + intern/path_util.cc IO_abstract_hierarchy_iterator.h IO_dupli_persistent_id.hh + IO_orientation.h IO_path_util.hh IO_path_util_types.h IO_types.h - IO_orientation.h intern/dupli_parent_finder.hh ) diff --git a/source/blender/io/common/IO_abstract_hierarchy_iterator.h b/source/blender/io/common/IO_abstract_hierarchy_iterator.h index a67cfe6a9d6..966eb640264 100644 --- a/source/blender/io/common/IO_abstract_hierarchy_iterator.h +++ b/source/blender/io/common/IO_abstract_hierarchy_iterator.h @@ -228,7 +228,7 @@ class AbstractHierarchyIterator { * writer is created it will also write the current iteration, to ensure the hierarchy is * complete. The `export_subset` option is only in effect when the writer already existed from a * previous iteration. */ - void set_export_subset(ExportSubset export_subset_); + void set_export_subset(ExportSubset export_subset); /* Convert the given name to something that is valid for the exported file format. * This base implementation is a no-op; override in a concrete subclass. */ @@ -267,7 +267,7 @@ class AbstractHierarchyIterator { /* These three functions create writers and call their write() method. */ void make_writers(const HierarchyContext *parent_context); void make_writer_object_data(const HierarchyContext *context); - void make_writers_particle_systems(const HierarchyContext *context); + void make_writers_particle_systems(const HierarchyContext *transform_context); /* Return the appropriate HierarchyContext for the data of the object represented by * object_context. */ @@ -332,7 +332,7 @@ class AbstractHierarchyIterator { virtual void release_writer(AbstractHierarchyWriter *writer) = 0; AbstractHierarchyWriter *get_writer(const std::string &export_path) const; - ExportChildren &graph_children(const HierarchyContext *parent_context); + ExportChildren &graph_children(const HierarchyContext *context); }; } // namespace blender::io diff --git a/source/blender/io/common/intern/path_util.cc b/source/blender/io/common/intern/path_util.cc index 902cf552bf0..18632b410f8 100644 --- a/source/blender/io/common/intern/path_util.cc +++ b/source/blender/io/common/intern/path_util.cc @@ -39,7 +39,7 @@ std::string path_reference(StringRefNull filepath, if (mode == PATH_REFERENCE_ABSOLUTE) { return filepath_abs; } - else if (mode == PATH_REFERENCE_RELATIVE) { + if (mode == PATH_REFERENCE_RELATIVE) { char rel_path[PATH_MAX]; BLI_strncpy(rel_path, filepath_abs, PATH_MAX); BLI_path_rel(rel_path, base_dst.c_str()); @@ -49,7 +49,7 @@ std::string path_reference(StringRefNull filepath, } return rel_path + 2; /* Skip blender's internal "//" prefix. */ } - else if (mode == PATH_REFERENCE_STRIP) { + if (mode == PATH_REFERENCE_STRIP) { return BLI_path_basename(filepath_abs); } BLI_assert_msg(false, "Invalid path reference mode"); diff --git a/source/blender/io/gpencil/gpencil_io.h b/source/blender/io/gpencil/gpencil_io.h index 215891e3e48..eb811fa2de8 100644 --- a/source/blender/io/gpencil/gpencil_io.h +++ b/source/blender/io/gpencil/gpencil_io.h @@ -35,6 +35,8 @@ typedef struct GpencilIOParams { /** Stroke sampling factor. */ float stroke_sample; int32_t resolution; + /** Filename to be used in new objects. */ + char filename[128]; } GpencilIOParams; /* GpencilIOParams->flag. */ diff --git a/source/blender/io/gpencil/intern/gpencil_io_base.cc b/source/blender/io/gpencil/intern/gpencil_io_base.cc index 05f1158c57d..b5838ad9485 100644 --- a/source/blender/io/gpencil/intern/gpencil_io_base.cc +++ b/source/blender/io/gpencil/intern/gpencil_io_base.cc @@ -91,8 +91,7 @@ void GpencilIO::prepare_camera_params(Scene *scene, const GpencilIOParams *ipara /* Camera rectangle. */ if ((rv3d_->persp == RV3D_CAMOB) || (force_camera_view)) { - render_x_ = (scene_->r.xsch * scene_->r.size) / 100; - render_y_ = (scene_->r.ysch * scene_->r.size) / 100; + BKE_render_resolution(&scene->r, false, &render_x_, &render_y_); ED_view3d_calc_camera_border(CTX_data_scene(params_.C), depsgraph_, @@ -258,7 +257,7 @@ float GpencilIO::stroke_point_radius_get(bGPDlayer *gpl, bGPDstroke *gps) /* Radius. */ bGPDstroke *gps_perimeter = BKE_gpencil_stroke_perimeter_from_view( - rv3d_, gpd_, gpl, gps, 3, diff_mat_.values); + rv3d_->viewmat, gpd_, gpl, gps, 3, diff_mat_.values, 0.0f); pt = &gps_perimeter->points[0]; const float2 screen_ex = gpencil_3D_point_to_2D(&pt->x); diff --git a/source/blender/io/gpencil/intern/gpencil_io_base.hh b/source/blender/io/gpencil/intern/gpencil_io_base.hh index a89b723ed6c..4987ab34ffc 100644 --- a/source/blender/io/gpencil/intern/gpencil_io_base.hh +++ b/source/blender/io/gpencil/intern/gpencil_io_base.hh @@ -58,8 +58,8 @@ class GpencilIO { struct Scene *scene_; struct RegionView3D *rv3d_; - int16_t winx_, winy_; - int16_t render_x_, render_y_; + int winx_, winy_; + int render_x_, render_y_; float camera_ratio_; rctf camera_rect_; diff --git a/source/blender/io/gpencil/intern/gpencil_io_capi.cc b/source/blender/io/gpencil/intern/gpencil_io_capi.cc index 84b273bc570..ac5f8cf7c8d 100644 --- a/source/blender/io/gpencil/intern/gpencil_io_capi.cc +++ b/source/blender/io/gpencil/intern/gpencil_io_capi.cc @@ -112,7 +112,7 @@ static bool gpencil_io_export_pdf(Depsgraph *depsgraph, continue; } - CFRA = i; + scene->r.cfra = i; BKE_scene_graph_update_for_newframe(depsgraph); exporter->prepare_camera_params(scene, iparams); exporter->frame_number_set(i); @@ -122,7 +122,7 @@ static bool gpencil_io_export_pdf(Depsgraph *depsgraph, result = exporter->write(); /* Back to original frame. */ exporter->frame_number_set(iparams->frame_cur); - CFRA = iparams->frame_cur; + scene->r.cfra = iparams->frame_cur; BKE_scene_camera_switch_update(scene); BKE_scene_graph_update_for_newframe(depsgraph); break; diff --git a/source/blender/io/gpencil/intern/gpencil_io_export_pdf.cc b/source/blender/io/gpencil/intern/gpencil_io_export_pdf.cc index 700d91791a8..463032ebb9d 100644 --- a/source/blender/io/gpencil/intern/gpencil_io_export_pdf.cc +++ b/source/blender/io/gpencil/intern/gpencil_io_export_pdf.cc @@ -192,7 +192,7 @@ void GpencilExporterPDF::export_gpencil_layers() } else { bGPDstroke *gps_perimeter = BKE_gpencil_stroke_perimeter_from_view( - rv3d_, gpd_, gpl, gps_duplicate, 3, diff_mat_.values); + rv3d_->viewmat, gpd_, gpl, gps_duplicate, 3, diff_mat_.values, 0.0f); /* Sample stroke. */ if (params_.stroke_sample > 0.0f) { diff --git a/source/blender/io/gpencil/intern/gpencil_io_export_svg.cc b/source/blender/io/gpencil/intern/gpencil_io_export_svg.cc index 2601ad05ea7..58f12e9b8b1 100644 --- a/source/blender/io/gpencil/intern/gpencil_io_export_svg.cc +++ b/source/blender/io/gpencil/intern/gpencil_io_export_svg.cc @@ -217,7 +217,7 @@ void GpencilExporterSVG::export_gpencil_layers() } else { bGPDstroke *gps_perimeter = BKE_gpencil_stroke_perimeter_from_view( - rv3d_, gpd_, gpl, gps_duplicate, 3, diff_mat_.values); + rv3d_->viewmat, gpd_, gpl, gps_duplicate, 3, diff_mat_.values, 0.0f); /* Sample stroke. */ if (params_.stroke_sample > 0.0f) { diff --git a/source/blender/io/gpencil/intern/gpencil_io_import_base.cc b/source/blender/io/gpencil/intern/gpencil_io_import_base.cc index 9b00fbaa027..6d4439243fd 100644 --- a/source/blender/io/gpencil/intern/gpencil_io_import_base.cc +++ b/source/blender/io/gpencil/intern/gpencil_io_import_base.cc @@ -14,6 +14,7 @@ #include "BKE_material.h" #include "ED_gpencil.h" +#include "ED_object.h" #include "gpencil_io_import_base.hh" @@ -27,10 +28,22 @@ GpencilImporter::GpencilImporter(const GpencilIOParams *iparams) : GpencilIO(ipa Object *GpencilImporter::create_object() { - const float *cur = scene_->cursor.location; + const float *cur_loc = scene_->cursor.location; + const float rot[3] = {0.0f}; ushort local_view_bits = (params_.v3d && params_.v3d->localvd) ? params_.v3d->local_view_uuid : (ushort)0; - Object *ob_gpencil = ED_gpencil_add_object(params_.C, cur, local_view_bits); + + Object *ob_gpencil = ED_object_add_type(params_.C, + OB_GPENCIL, + (params_.filename[0] != '\0') ? params_.filename : + nullptr, + cur_loc, + rot, + false, + local_view_bits); + + /* Set object defaults. */ + ED_gpencil_add_defaults(params_.C, ob_gpencil); return ob_gpencil; } diff --git a/source/blender/io/stl/CMakeLists.txt b/source/blender/io/stl/CMakeLists.txt index e0c48bbbf7e..3a21da5c579 100644 --- a/source/blender/io/stl/CMakeLists.txt +++ b/source/blender/io/stl/CMakeLists.txt @@ -24,16 +24,16 @@ set(INC_SYS set(SRC IO_stl.cc - importer/stl_import_mesh.cc + importer/stl_import.cc importer/stl_import_ascii_reader.cc importer/stl_import_binary_reader.cc - importer/stl_import.cc + importer/stl_import_mesh.cc IO_stl.h - importer/stl_import_mesh.hh + importer/stl_import.hh importer/stl_import_ascii_reader.hh importer/stl_import_binary_reader.hh - importer/stl_import.hh + importer/stl_import_mesh.hh ) set(LIB diff --git a/source/blender/io/stl/importer/stl_import.cc b/source/blender/io/stl/importer/stl_import.cc index f358598a216..097d14b038c 100644 --- a/source/blender/io/stl/importer/stl_import.cc +++ b/source/blender/io/stl/importer/stl_import.cc @@ -29,6 +29,17 @@ namespace blender::io::stl { +void stl_import_report_error(FILE *file) +{ + fprintf(stderr, "STL Importer: failed to read file"); + if (feof(file)) { + fprintf(stderr, ", end of file reached.\n"); + } + else if (ferror(file)) { + perror("Error"); + } +} + void importer_main(bContext *C, const STLImportParams &import_params) { Main *bmain = CTX_data_main(C); @@ -56,7 +67,10 @@ void importer_main(Main *bmain, uint32_t num_tri = 0; size_t file_size = BLI_file_size(import_params.filepath); fseek(file, BINARY_HEADER_SIZE, SEEK_SET); - fread(&num_tri, sizeof(uint32_t), 1, file); + if (fread(&num_tri, sizeof(uint32_t), 1, file) != 1) { + stl_import_report_error(file); + return; + } bool is_ascii_stl = (file_size != (BINARY_HEADER_SIZE + 4 + BINARY_STRIDE * num_tri)); /* Name used for both mesh and object. */ @@ -64,7 +78,7 @@ void importer_main(Main *bmain, BLI_strncpy(ob_name, BLI_path_basename(import_params.filepath), FILE_MAX); BLI_path_extension_replace(ob_name, FILE_MAX, ""); - Mesh *mesh; + Mesh *mesh = nullptr; if (is_ascii_stl) { mesh = read_stl_ascii(import_params.filepath, bmain, ob_name, import_params.use_facet_normal); } @@ -72,6 +86,11 @@ void importer_main(Main *bmain, mesh = read_stl_binary(file, bmain, ob_name, import_params.use_facet_normal); } + if (mesh == nullptr) { + fprintf(stderr, "STL Importer: Failed to import mesh '%s'\n", import_params.filepath); + return; + } + if (import_params.use_mesh_validate) { bool verbose_validate = false; #ifdef DEBUG diff --git a/source/blender/io/stl/importer/stl_import.hh b/source/blender/io/stl/importer/stl_import.hh index 377544c26af..a5d252248a8 100644 --- a/source/blender/io/stl/importer/stl_import.hh +++ b/source/blender/io/stl/importer/stl_import.hh @@ -10,6 +10,8 @@ namespace blender::io::stl { +void stl_import_report_error(FILE *file); + /* Main import function used from within Blender. */ void importer_main(bContext *C, const STLImportParams &import_params); diff --git a/source/blender/io/stl/importer/stl_import_binary_reader.cc b/source/blender/io/stl/importer/stl_import_binary_reader.cc index 6eaed16160e..fb9dcea0a1d 100644 --- a/source/blender/io/stl/importer/stl_import_binary_reader.cc +++ b/source/blender/io/stl/importer/stl_import_binary_reader.cc @@ -15,6 +15,7 @@ #include "DNA_mesh_types.h" +#include "stl_import.hh" #include "stl_import_binary_reader.hh" #include "stl_import_mesh.hh" @@ -33,7 +34,11 @@ Mesh *read_stl_binary(FILE *file, Main *bmain, char *mesh_name, bool use_custom_ const int chunk_size = 1024; uint32_t num_tris = 0; fseek(file, BINARY_HEADER_SIZE, SEEK_SET); - fread(&num_tris, sizeof(uint32_t), 1, file); + if (fread(&num_tris, sizeof(uint32_t), 1, file) != 1) { + stl_import_report_error(file); + return nullptr; + } + if (num_tris == 0) { return BKE_mesh_add(bmain, mesh_name); } diff --git a/source/blender/io/stl/importer/stl_import_mesh.cc b/source/blender/io/stl/importer/stl_import_mesh.cc index 7de8239b233..de993cd2f27 100644 --- a/source/blender/io/stl/importer/stl_import_mesh.cc +++ b/source/blender/io/stl/importer/stl_import_mesh.cc @@ -63,11 +63,11 @@ void STLMeshHelper::add_triangle(const float3 &a, Mesh *STLMeshHelper::to_mesh(Main *bmain, char *mesh_name) { if (degenerate_tris_num_ > 0) { - std::cout << "STL Importer: " << degenerate_tris_num_ << "degenerate triangles were removed" + std::cout << "STL Importer: " << degenerate_tris_num_ << " degenerate triangles were removed" << std::endl; } if (duplicate_tris_num_ > 0) { - std::cout << "STL Importer: " << duplicate_tris_num_ << "duplicate triangles were removed" + std::cout << "STL Importer: " << duplicate_tris_num_ << " duplicate triangles were removed" << std::endl; } @@ -76,27 +76,26 @@ Mesh *STLMeshHelper::to_mesh(Main *bmain, char *mesh_name) id_us_min(&mesh->id); mesh->totvert = verts_.size(); - mesh->mvert = static_cast<MVert *>( - CustomData_add_layer(&mesh->vdata, CD_MVERT, CD_CALLOC, nullptr, mesh->totvert)); + CustomData_add_layer(&mesh->vdata, CD_MVERT, CD_SET_DEFAULT, nullptr, mesh->totvert); + MutableSpan<MVert> verts = mesh->verts_for_write(); for (int i = 0; i < mesh->totvert; i++) { - copy_v3_v3(mesh->mvert[i].co, verts_[i]); + copy_v3_v3(verts[i].co, verts_[i]); } mesh->totpoly = tris_.size(); mesh->totloop = tris_.size() * 3; - mesh->mpoly = static_cast<MPoly *>( - CustomData_add_layer(&mesh->pdata, CD_MPOLY, CD_CALLOC, nullptr, mesh->totpoly)); - mesh->mloop = static_cast<MLoop *>( - CustomData_add_layer(&mesh->ldata, CD_MLOOP, CD_CALLOC, nullptr, mesh->totloop)); - + CustomData_add_layer(&mesh->pdata, CD_MPOLY, CD_SET_DEFAULT, nullptr, mesh->totpoly); + CustomData_add_layer(&mesh->ldata, CD_MLOOP, CD_SET_DEFAULT, nullptr, mesh->totloop); + MutableSpan<MPoly> polys = mesh->polys_for_write(); + MutableSpan<MLoop> loops = mesh->loops_for_write(); threading::parallel_for(tris_.index_range(), 2048, [&](IndexRange tris_range) { for (const int i : tris_range) { - mesh->mpoly[i].loopstart = 3 * i; - mesh->mpoly[i].totloop = 3; + polys[i].loopstart = 3 * i; + polys[i].totloop = 3; - mesh->mloop[3 * i].v = tris_[i].v1; - mesh->mloop[3 * i + 1].v = tris_[i].v2; - mesh->mloop[3 * i + 2].v = tris_[i].v3; + loops[3 * i].v = tris_[i].v1; + loops[3 * i + 1].v = tris_[i].v2; + loops[3 * i + 2].v = tris_[i].v3; } }); diff --git a/source/blender/io/usd/intern/usd_capi_export.cc b/source/blender/io/usd/intern/usd_capi_export.cc index 2049c631671..1033f85181c 100644 --- a/source/blender/io/usd/intern/usd_capi_export.cc +++ b/source/blender/io/usd/intern/usd_capi_export.cc @@ -27,6 +27,7 @@ #include "BLI_fileops.h" #include "BLI_path_util.h" #include "BLI_string.h" +#include "BLI_timeit.hh" #include "WM_api.h" #include "WM_types.h" @@ -42,8 +43,17 @@ struct ExportJobData { USDExportParams params; bool export_ok; + timeit::TimePoint start_time; }; +static void report_job_duration(const ExportJobData *data) +{ + timeit::Nanoseconds duration = timeit::Clock::now() - data->start_time; + std::cout << "USD export of '" << data->filepath << "' took "; + timeit::print_duration(duration); + std::cout << '\n'; +} + static void export_startjob(void *customdata, /* Cannot be const, this function implements wm_jobs_start_callback. * NOLINTNEXTLINE: readability-non-const-parameter. */ @@ -53,6 +63,7 @@ static void export_startjob(void *customdata, { ExportJobData *data = static_cast<ExportJobData *>(customdata); data->export_ok = false; + data->start_time = timeit::Clock::now(); G.is_rendering = true; WM_set_locked_interface(data->wm, true); @@ -72,7 +83,7 @@ static void export_startjob(void *customdata, *do_update = true; /* For restoring the current frame after exporting animation is done. */ - const int orig_frame = CFRA; + const int orig_frame = scene->r.cfra; pxr::UsdStageRefPtr usd_stage = pxr::UsdStage::CreateNew(data->filepath); if (!usd_stage) { @@ -129,8 +140,8 @@ static void export_startjob(void *customdata, usd_stage->GetRootLayer()->Save(); /* Finish up by going back to the keyframe that was current before we started. */ - if (CFRA != orig_frame) { - CFRA = orig_frame; + if (scene->r.cfra != orig_frame) { + scene->r.cfra = orig_frame; BKE_scene_graph_update_for_newframe(data->depsgraph); } @@ -151,6 +162,7 @@ static void export_endjob(void *customdata) G.is_rendering = false; WM_set_locked_interface(data->wm, false); + report_job_duration(data); } } // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_capi_import.cc b/source/blender/io/usd/intern/usd_capi_import.cc index 29b256125f0..03af3aed2d0 100644 --- a/source/blender/io/usd/intern/usd_capi_import.cc +++ b/source/blender/io/usd/intern/usd_capi_import.cc @@ -30,6 +30,7 @@ #include "BLI_math_rotation.h" #include "BLI_path_util.h" #include "BLI_string.h" +#include "BLI_timeit.hh" #include "DEG_depsgraph.h" #include "DEG_depsgraph_build.h" @@ -132,8 +133,17 @@ struct ImportJobData { char error_code; bool was_canceled; bool import_ok; + timeit::TimePoint start_time; }; +static void report_job_duration(const ImportJobData *data) +{ + timeit::Nanoseconds duration = timeit::Clock::now() - data->start_time; + std::cout << "USD import of '" << data->filepath << "' took "; + timeit::print_duration(duration); + std::cout << '\n'; +} + static void import_startjob(void *customdata, short *stop, short *do_update, float *progress) { ImportJobData *data = static_cast<ImportJobData *>(customdata); @@ -143,6 +153,7 @@ static void import_startjob(void *customdata, short *stop, short *do_update, flo data->progress = progress; data->was_canceled = false; data->archive = nullptr; + data->start_time = timeit::Clock::now(); WM_set_locked_interface(data->wm, true); G.is_break = false; @@ -207,6 +218,7 @@ static void import_startjob(void *customdata, short *stop, short *do_update, flo data->scene->r.efra = stage->GetEndTimeCode(); } + *data->do_update = true; *data->progress = 0.15f; USDStageReader *archive = new USDStageReader(stage, data->params, data->settings); @@ -215,13 +227,32 @@ static void import_startjob(void *customdata, short *stop, short *do_update, flo archive->collect_readers(data->bmain); + *data->do_update = true; *data->progress = 0.2f; const float size = static_cast<float>(archive->readers().size()); size_t i = 0; - /* Setup parenthood */ + /* Sort readers by name: when creating a lot of objects in Blender, + * it is much faster if the order is sorted by name. */ + archive->sort_readers(); + *data->do_update = true; + *data->progress = 0.25f; + + /* Create blender objects. */ + for (USDPrimReader *reader : archive->readers()) { + if (!reader) { + continue; + } + reader->create_object(data->bmain, 0.0); + if ((++i & 1023) == 0) { + *data->do_update = true; + *data->progress = 0.25f + 0.25f * (i / size); + } + } + /* Setup parenthood and read actual object data. */ + i = 0; for (USDPrimReader *reader : archive->readers()) { if (!reader) { @@ -241,7 +272,7 @@ static void import_startjob(void *customdata, short *stop, short *do_update, flo ob->parent = parent->object(); } - *data->progress = 0.2f + 0.8f * (++i / size); + *data->progress = 0.5f + 0.5f * (++i / size); *data->do_update = true; if (G.is_break) { @@ -277,7 +308,6 @@ static void import_endjob(void *customdata) } } else if (data->archive) { - /* Add object to scene. */ Base *base; LayerCollection *lc; ViewLayer *view_layer = data->view_layer; @@ -286,20 +316,30 @@ static void import_endjob(void *customdata) lc = BKE_layer_collection_get_active(view_layer); + /* Add all objects to the collection (don't do sync for each object). */ + BKE_layer_collection_resync_forbid(); for (USDPrimReader *reader : data->archive->readers()) { - if (!reader) { continue; } - Object *ob = reader->object(); - if (!ob) { continue; } - BKE_collection_object_add(data->bmain, lc->collection, ob); + } + /* Sync the collection, and do view layer operations. */ + BKE_layer_collection_resync_allow(); + BKE_main_collection_sync(data->bmain); + for (USDPrimReader *reader : data->archive->readers()) { + if (!reader) { + continue; + } + Object *ob = reader->object(); + if (!ob) { + continue; + } base = BKE_view_layer_base_find(view_layer, ob); /* TODO: is setting active needed? */ BKE_view_layer_base_select_and_set_active(view_layer, base); @@ -328,6 +368,7 @@ static void import_endjob(void *customdata) } WM_main_add_notifier(NC_SCENE | ND_FRAME, data->scene); + report_job_duration(data); } static void import_freejob(void *user_data) diff --git a/source/blender/io/usd/intern/usd_reader_material.cc b/source/blender/io/usd/intern/usd_reader_material.cc index 8feceee55ed..3546beb022c 100644 --- a/source/blender/io/usd/intern/usd_reader_material.cc +++ b/source/blender/io/usd/intern/usd_reader_material.cc @@ -4,13 +4,17 @@ #include "usd_reader_material.h" #include "BKE_image.h" +#include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_material.h" #include "BKE_node.h" #include "BKE_node_tree_update.h" +#include "BLI_fileops.h" #include "BLI_math_vector.h" +#include "BLI_path_util.h" #include "BLI_string.h" +#include "BLI_vector.hh" #include "DNA_material_types.h" @@ -94,6 +98,60 @@ static void link_nodes( nodeAddLink(ntree, source, source_socket, dest, dest_socket); } +/* Returns a layer handle retrieved from the given attribute's property specs. + * Note that the returned handle may be invalid if no layer could be found. */ +static pxr::SdfLayerHandle get_layer_handle(const pxr::UsdAttribute &attribute) +{ + for (auto PropertySpec : attribute.GetPropertyStack(pxr::UsdTimeCode::EarliestTime())) { + if (PropertySpec->HasDefaultValue() || + PropertySpec->GetLayer()->GetNumTimeSamplesForPath(PropertySpec->GetPath()) > 0) { + return PropertySpec->GetLayer(); + } + } + + return pxr::SdfLayerHandle(); +} + +static bool is_udim_path(const std::string &path) +{ + return path.find("<UDIM>") != std::string::npos; +} + +/* For the given UDIM path (assumed to contain the UDIM token), returns an array + * containing valid tile indices. */ +static blender::Vector<int> get_udim_tiles(const std::string &file_path) +{ + char base_udim_path[FILE_MAX]; + BLI_strncpy(base_udim_path, file_path.c_str(), sizeof(base_udim_path)); + + blender::Vector<int> udim_tiles; + + /* Extract the tile numbers from all files on disk. */ + ListBase tiles = {nullptr, nullptr}; + int tile_start, tile_range; + bool result = BKE_image_get_tile_info(base_udim_path, &tiles, &tile_start, &tile_range); + if (result) { + LISTBASE_FOREACH (LinkData *, tile, &tiles) { + int tile_number = POINTER_AS_INT(tile->data); + udim_tiles.append(tile_number); + } + } + + BLI_freelistN(&tiles); + + return udim_tiles; +} + +/* Add tiles with the given indices to the given image. */ +static void add_udim_tiles(Image *image, const blender::Vector<int> &indices) +{ + image->source = IMA_SRC_TILED; + + for (int tile_number : indices) { + BKE_image_add_tile(image, tile_number, nullptr); + } +} + /* Returns true if the given shader may have opacity < 1.0, based * on heuristics. */ static bool needs_blend(const pxr::UsdShadeShader &usd_shader) @@ -266,6 +324,7 @@ Material *USDMaterialReader::add_material(const pxr::UsdShadeMaterial &usd_mater /* Create the material. */ Material *mtl = BKE_material_add(bmain_, mtl_name.c_str()); + id_us_min(&mtl->id); /* Get the UsdPreviewSurface shader source for the material, * if there is one. */ @@ -294,8 +353,7 @@ void USDMaterialReader::import_usd_preview(Material *mtl, * and output shaders. */ /* Add the node tree. */ - bNodeTree *ntree = ntreeAddTree(nullptr, "Shader Nodetree", "ShaderNodeTree"); - mtl->nodetree = ntree; + bNodeTree *ntree = ntreeAddTreeEmbedded(nullptr, &mtl->id, "Shader Nodetree", "ShaderNodeTree"); mtl->use_nodes = true; /* Create the Principled BSDF shader node. */ @@ -601,11 +659,31 @@ void USDMaterialReader::load_tex_image(const pxr::UsdShadeShader &usd_shader, const pxr::SdfAssetPath &asset_path = file_val.Get<pxr::SdfAssetPath>(); std::string file_path = asset_path.GetResolvedPath(); if (file_path.empty()) { + /* No resolved path, so use the asset path (usually + * necessary for UDIM paths). */ + file_path = asset_path.GetAssetPath(); + + /* Texture paths are frequently relative to the USD, so get + * the absolute path. */ + if (pxr::SdfLayerHandle layer_handle = get_layer_handle(file_input.GetAttr())) { + file_path = layer_handle->ComputeAbsolutePath(file_path); + } + } + + if (file_path.empty()) { std::cerr << "WARNING: Couldn't resolve image asset '" << asset_path << "' for Texture Image node." << std::endl; return; } + /* If this is a UDIM texture, this will store the + * UDIM tile indices. */ + blender::Vector<int> udim_tiles; + + if (is_udim_path(file_path)) { + udim_tiles = get_udim_tiles(file_path); + } + const char *im_file = file_path.c_str(); Image *image = BKE_image_load_exists(bmain_, im_file); if (!image) { @@ -614,6 +692,10 @@ void USDMaterialReader::load_tex_image(const pxr::UsdShadeShader &usd_shader, return; } + if (udim_tiles.size() > 0) { + add_udim_tiles(image, udim_tiles); + } + tex_image->id = &image->id; /* Set texture color space. diff --git a/source/blender/io/usd/intern/usd_reader_mesh.cc b/source/blender/io/usd/intern/usd_reader_mesh.cc index 368d0e1bab9..0b96cd8ce90 100644 --- a/source/blender/io/usd/intern/usd_reader_mesh.cc +++ b/source/blender/io/usd/intern/usd_reader_mesh.cc @@ -6,6 +6,7 @@ #include "usd_reader_mesh.h" #include "usd_reader_material.h" +#include "BKE_attribute.hh" #include "BKE_customdata.h" #include "BKE_main.h" #include "BKE_material.h" @@ -64,12 +65,26 @@ static void build_mat_map(const Main *bmain, std::map<std::string, Material *> * static pxr::UsdShadeMaterial compute_bound_material(const pxr::UsdPrim &prim) { - return pxr::UsdShadeMaterialBindingAPI(prim).ComputeBoundMaterial(); + pxr::UsdShadeMaterialBindingAPI api = pxr::UsdShadeMaterialBindingAPI(prim); + + /* Compute generically bound ('allPurpose') materials. */ + pxr::UsdShadeMaterial mtl = api.ComputeBoundMaterial(); + + /* If no generic material could be resolved, also check for 'preview' and + * 'full' purpose materials as fallbacks. */ + if (!mtl) { + mtl = api.ComputeBoundMaterial(pxr::UsdShadeTokens->preview); + } + + if (!mtl) { + mtl = api.ComputeBoundMaterial(pxr::UsdShadeTokens->full); + } + + return mtl; } -/* Returns an existing Blender material that corresponds to the USD - * material with with the given path. Returns null if no such material - * exists. */ +/* Returns an existing Blender material that corresponds to the USD material with the given path. + * Returns null if no such material exists. */ static Material *find_existing_material( const pxr::SdfPath &usd_mat_path, const USDImportParams ¶ms, @@ -87,11 +102,9 @@ static Material *find_existing_material( if (mat_iter != mat_map.end()) { return mat_iter->second; } - else { - /* We can't find the Blender material which was previously created for this USD - * material, which should never happen. */ - BLI_assert_unreachable(); - } + /* We can't find the Blender material which was previously created for this USD + * material, which should never happen. */ + BLI_assert_unreachable(); } } else { @@ -111,37 +124,25 @@ static void assign_materials(Main *bmain, const std::map<pxr::SdfPath, int> &mat_index_map, const USDImportParams ¶ms, pxr::UsdStageRefPtr stage, + std::map<std::string, Material *> &mat_name_to_mat, std::map<std::string, std::string> &usd_path_to_mat_name) { if (!(stage && bmain && ob)) { return; } - bool can_assign = true; - std::map<pxr::SdfPath, int>::const_iterator it = mat_index_map.begin(); - - int matcount = 0; - for (; it != mat_index_map.end(); ++it, matcount++) { - if (!BKE_object_material_slot_add(bmain, ob)) { - can_assign = false; - break; - } - } - - if (!can_assign) { + if (mat_index_map.size() > MAXMAT) { return; } - /* TODO(kevin): use global map? */ - std::map<std::string, Material *> mat_map; - build_mat_map(bmain, &mat_map); - blender::io::usd::USDMaterialReader mat_reader(params, bmain); - for (it = mat_index_map.begin(); it != mat_index_map.end(); ++it) { + for (std::map<pxr::SdfPath, int>::const_iterator it = mat_index_map.begin(); + it != mat_index_map.end(); + ++it) { Material *assigned_mat = find_existing_material( - it->first, params, mat_map, usd_path_to_mat_name); + it->first, params, mat_name_to_mat, usd_path_to_mat_name); if (!assigned_mat) { /* Blender material doesn't exist, so create it now. */ @@ -165,7 +166,7 @@ static void assign_materials(Main *bmain, } const std::string mat_name = pxr::TfMakeValidIdentifier(assigned_mat->id.name + 2); - mat_map[mat_name] = assigned_mat; + mat_name_to_mat[mat_name] = assigned_mat; if (params.mtl_name_collision_mode == USD_MTL_NAME_COLLISION_MAKE_UNIQUE) { /* Record the name of the Blender material we created for the USD material @@ -175,13 +176,16 @@ static void assign_materials(Main *bmain, } if (assigned_mat) { - BKE_object_material_assign(bmain, ob, assigned_mat, it->second, BKE_MAT_ASSIGN_OBDATA); + BKE_object_material_assign_single_obdata(bmain, ob, assigned_mat, it->second); } else { /* This shouldn't happen. */ std::cout << "WARNING: Couldn't assign material " << it->first << std::endl; } } + if (ob->totcol > 0) { + ob->actcol = 1; + } } } // namespace utils @@ -207,7 +211,8 @@ static void *add_customdata_cb(Mesh *mesh, const char *name, const int data_type /* Create a new layer. */ numloops = mesh->totloop; - cd_ptr = CustomData_add_layer_named(loopdata, cd_data_type, CD_DEFAULT, nullptr, numloops, name); + cd_ptr = CustomData_add_layer_named( + loopdata, cd_data_type, CD_SET_DEFAULT, nullptr, numloops, name); return cd_ptr; } @@ -307,18 +312,17 @@ bool USDMeshReader::topology_changed(const Mesh *existing_mesh, const double mot void USDMeshReader::read_mpolys(Mesh *mesh) { - MPoly *mpolys = mesh->mpoly; - MLoop *mloops = mesh->mloop; + MutableSpan<MPoly> polys = mesh->polys_for_write(); + MutableSpan<MLoop> loops = mesh->loops_for_write(); int loop_index = 0; for (int i = 0; i < face_counts_.size(); i++) { const int face_size = face_counts_[i]; - MPoly &poly = mpolys[i]; + MPoly &poly = polys[i]; poly.loopstart = loop_index; poly.totloop = face_size; - poly.mat_nr = 0; /* Polygons are always assumed to be smooth-shaded. If the mesh should be flat-shaded, * this is encoded in custom loop normals. */ @@ -327,12 +331,12 @@ void USDMeshReader::read_mpolys(Mesh *mesh) if (is_left_handed_) { int loop_end_index = loop_index + (face_size - 1); for (int f = 0; f < face_size; ++f, ++loop_index) { - mloops[loop_index].v = face_indices_[loop_end_index - f]; + loops[loop_index].v = face_indices_[loop_end_index - f]; } } else { for (int f = 0; f < face_size; ++f, ++loop_index) { - mloops[loop_index].v = face_indices_[loop_index]; + loops[loop_index].v = face_indices_[loop_index]; } } } @@ -396,6 +400,7 @@ void USDMeshReader::read_uvs(Mesh *mesh, const double motionSampleTime, const bo } } + const Span<MLoop> loops = mesh->loops(); for (int i = 0; i < face_counts_.size(); i++) { const int face_size = face_counts_[i]; @@ -431,7 +436,7 @@ void USDMeshReader::read_uvs(Mesh *mesh, const double motionSampleTime, const bo /* For Vertex interpolation, use the vertex index. */ int usd_uv_index = sample.interpolation == pxr::UsdGeomTokens->vertex ? - mesh->mloop[loop_index].v : + loops[loop_index].v : loop_index; if (usd_uv_index >= sample.uvs.size()) { @@ -511,24 +516,23 @@ void USDMeshReader::read_colors(Mesh *mesh, const double motionSampleTime) MLoopCol *colors = static_cast<MLoopCol *>(cd_ptr); - mesh->mloopcol = colors; - - MPoly *poly = mesh->mpoly; - - for (int i = 0, e = mesh->totpoly; i < e; ++i, ++poly) { - for (int j = 0; j < poly->totloop; ++j) { - int loop_index = poly->loopstart + j; + const Span<MPoly> polys = mesh->polys(); + const Span<MLoop> loops = mesh->loops(); + for (const int i : polys.index_range()) { + const MPoly &poly = polys[i]; + for (int j = 0; j < poly.totloop; ++j) { + int loop_index = poly.loopstart + j; /* Default for constant varying interpolation. */ int usd_index = 0; if (interp == pxr::UsdGeomTokens->vertex) { - usd_index = mesh->mloop[loop_index].v; + usd_index = loops[loop_index].v; } else if (interp == pxr::UsdGeomTokens->faceVarying) { - usd_index = poly->loopstart; + usd_index = poly.loopstart; if (is_left_handed_) { - usd_index += poly->totloop - 1 - j; + usd_index += poly.totloop - 1 - j; } else { usd_index += j; @@ -576,7 +580,7 @@ void USDMeshReader::read_vertex_creases(Mesh *mesh, const double motionSampleTim } float *creases = static_cast<float *>( - CustomData_add_layer(&mesh->vdata, CD_CREASE, CD_DEFAULT, nullptr, mesh->totvert)); + CustomData_add_layer(&mesh->vdata, CD_CREASE, CD_SET_DEFAULT, nullptr, mesh->totvert)); for (size_t i = 0; i < corner_indices.size(); i++) { creases[corner_indices[i]] = corner_sharpnesses[i]; @@ -626,15 +630,15 @@ void USDMeshReader::process_normals_face_varying(Mesh *mesh) float(*lnors)[3] = static_cast<float(*)[3]>( MEM_malloc_arrayN(loop_count, sizeof(float[3]), "USD::FaceNormals")); - MPoly *mpoly = mesh->mpoly; + const Span<MPoly> polys = mesh->polys(); + for (const int i : polys.index_range()) { + const MPoly &poly = polys[i]; + for (int j = 0; j < poly.totloop; j++) { + int blender_index = poly.loopstart + j; - for (int i = 0, e = mesh->totpoly; i < e; ++i, ++mpoly) { - for (int j = 0; j < mpoly->totloop; j++) { - int blender_index = mpoly->loopstart + j; - - int usd_index = mpoly->loopstart; + int usd_index = poly.loopstart; if (is_left_handed_) { - usd_index += mpoly->totloop - 1 - j; + usd_index += poly.totloop - 1 - j; } else { usd_index += j; @@ -667,12 +671,11 @@ void USDMeshReader::process_normals_uniform(Mesh *mesh) float(*lnors)[3] = static_cast<float(*)[3]>( MEM_malloc_arrayN(mesh->totloop, sizeof(float[3]), "USD::FaceNormals")); - MPoly *mpoly = mesh->mpoly; - - for (int i = 0, e = mesh->totpoly; i < e; ++i, ++mpoly) { - - for (int j = 0; j < mpoly->totloop; j++) { - int loop_index = mpoly->loopstart + j; + const Span<MPoly> polys = mesh->polys(); + for (const int i : polys.index_range()) { + const MPoly &poly = polys[i]; + for (int j = 0; j < poly.totloop; j++) { + int loop_index = poly.loopstart + j; lnors[loop_index][0] = normals_[i][0]; lnors[loop_index][1] = normals_[i][1]; lnors[loop_index][2] = normals_[i][2]; @@ -695,8 +698,9 @@ void USDMeshReader::read_mesh_sample(ImportSettings *settings, * in code that expect this data to be there. */ if (new_mesh || (settings->read_flag & MOD_MESHSEQ_READ_VERT) != 0) { + MutableSpan<MVert> verts = mesh->verts_for_write(); for (int i = 0; i < positions_.size(); i++) { - MVert &mvert = mesh->mvert[i]; + MVert &mvert = verts[i]; mvert.co[0] = positions_[i][0]; mvert.co[1] = positions_[i][1]; mvert.co[2] = positions_[i][2]; @@ -734,10 +738,9 @@ void USDMeshReader::read_mesh_sample(ImportSettings *settings, } } -void USDMeshReader::assign_facesets_to_mpoly(double motionSampleTime, - MPoly *mpoly, - const int /* totpoly */, - std::map<pxr::SdfPath, int> *r_mat_map) +void USDMeshReader::assign_facesets_to_material_indices(double motionSampleTime, + MutableSpan<int> material_indices, + std::map<pxr::SdfPath, int> *r_mat_map) { if (r_mat_map == nullptr) { return; @@ -777,9 +780,8 @@ void USDMeshReader::assign_facesets_to_mpoly(double motionSampleTime, pxr::VtIntArray indices; indicesAttribute.Get(&indices, motionSampleTime); - for (int i = 0; i < indices.size(); i++) { - MPoly &poly = mpoly[indices[i]]; - poly.mat_nr = mat_idx; + for (const int i : indices) { + material_indices[i] = mat_idx; } } } @@ -804,12 +806,22 @@ void USDMeshReader::readFaceSetsSample(Main *bmain, Mesh *mesh, const double mot } std::map<pxr::SdfPath, int> mat_map; - assign_facesets_to_mpoly(motionSampleTime, mesh->mpoly, mesh->totpoly, &mat_map); + + bke::MutableAttributeAccessor attributes = mesh->attributes_for_write(); + bke::SpanAttributeWriter<int> material_indices = + attributes.lookup_or_add_for_write_only_span<int>("material_index", ATTR_DOMAIN_FACE); + this->assign_facesets_to_material_indices(motionSampleTime, material_indices.span, &mat_map); + material_indices.finish(); + /* Build material name map if it's not built yet. */ + if (this->settings_->mat_name_to_mat.empty()) { + utils::build_mat_map(bmain, &this->settings_->mat_name_to_mat); + } utils::assign_materials(bmain, object_, mat_map, this->import_params_, this->prim_.GetStage(), + this->settings_->mat_name_to_mat, this->settings_->usd_path_to_mat_name); } @@ -891,8 +903,7 @@ Mesh *USDMeshReader::read_mesh(Mesh *existing_mesh, existing_mesh, positions_.size(), 0, 0, face_indices_.size(), face_counts_.size()); for (pxr::TfToken token : uv_tokens) { - void *cd_ptr = add_customdata_cb(active_mesh, token.GetText(), CD_MLOOPUV); - active_mesh->mloopuv = static_cast<MLoopUV *>(cd_ptr); + add_customdata_cb(active_mesh, token.GetText(), CD_MLOOPUV); } } @@ -902,10 +913,14 @@ Mesh *USDMeshReader::read_mesh(Mesh *existing_mesh, /* Here we assume that the number of materials doesn't change, i.e. that * the material slots that were created when the object was loaded from * USD are still valid now. */ - size_t num_polys = active_mesh->totpoly; - if (num_polys > 0 && import_params_.import_materials) { + MutableSpan<MPoly> polys = active_mesh->polys_for_write(); + if (!polys.is_empty() && import_params_.import_materials) { std::map<pxr::SdfPath, int> mat_map; - assign_facesets_to_mpoly(motionSampleTime, active_mesh->mpoly, num_polys, &mat_map); + bke::MutableAttributeAccessor attributes = active_mesh->attributes_for_write(); + bke::SpanAttributeWriter<int> material_indices = + attributes.lookup_or_add_for_write_only_span<int>("material_index", ATTR_DOMAIN_FACE); + assign_facesets_to_material_indices(motionSampleTime, material_indices.span, &mat_map); + material_indices.finish(); } } diff --git a/source/blender/io/usd/intern/usd_reader_mesh.h b/source/blender/io/usd/intern/usd_reader_mesh.h index 5e33ce8b5e8..181fd5ebf79 100644 --- a/source/blender/io/usd/intern/usd_reader_mesh.h +++ b/source/blender/io/usd/intern/usd_reader_mesh.h @@ -3,13 +3,13 @@ * Modifications Copyright 2021 Tangent Animation and. NVIDIA Corporation. All rights reserved. */ #pragma once +#include "BLI_span.hh" + #include "usd.h" #include "usd_reader_geom.h" #include "pxr/usd/usdGeom/mesh.h" -struct MPoly; - namespace blender::io::usd { class USDMeshReader : public USDGeomReader { @@ -61,10 +61,9 @@ class USDMeshReader : public USDGeomReader { /** Set USD uniform (per-face) normals as Blender loop normals. */ void process_normals_uniform(Mesh *mesh); void readFaceSetsSample(Main *bmain, Mesh *mesh, double motionSampleTime); - void assign_facesets_to_mpoly(double motionSampleTime, - struct MPoly *mpoly, - int totpoly, - std::map<pxr::SdfPath, int> *r_mat_map); + void assign_facesets_to_material_indices(double motionSampleTime, + MutableSpan<int> material_indices, + std::map<pxr::SdfPath, int> *r_mat_map); void read_mpolys(Mesh *mesh); void read_uvs(Mesh *mesh, double motionSampleTime, bool load_uvs = false); diff --git a/source/blender/io/usd/intern/usd_reader_prim.h b/source/blender/io/usd/intern/usd_reader_prim.h index f2df00accf6..c44c4a14ad7 100644 --- a/source/blender/io/usd/intern/usd_reader_prim.h +++ b/source/blender/io/usd/intern/usd_reader_prim.h @@ -11,6 +11,7 @@ #include <string> struct Main; +struct Material; struct Object; namespace blender::io::usd { @@ -42,6 +43,10 @@ struct ImportSettings { * of what the importer is doing. This is necessary even * when all the other import settings are to remain const. */ mutable std::map<std::string, std::string> usd_path_to_mat_name; + /* Map a material name to Blender material. + * This map is updated by readers during stage traversal, + * and is mutable similar to the map above. */ + mutable std::map<std::string, Material *> mat_name_to_mat; ImportSettings() : do_convert_mat(false), diff --git a/source/blender/io/usd/intern/usd_reader_stage.cc b/source/blender/io/usd/intern/usd_reader_stage.cc index 583c58a1356..df75be849e2 100644 --- a/source/blender/io/usd/intern/usd_reader_stage.cc +++ b/source/blender/io/usd/intern/usd_reader_stage.cc @@ -28,6 +28,9 @@ #include <iostream> +#include "BLI_sort.hh" +#include "BLI_string.h" + namespace blender::io::usd { USDStageReader::USDStageReader(pxr::UsdStageRefPtr stage, @@ -252,8 +255,6 @@ USDPrimReader *USDStageReader::collect_readers(Main *bmain, const pxr::UsdPrim & return nullptr; } - reader->create_object(bmain, 0.0); - readers_.push_back(reader); reader->incref(); @@ -310,4 +311,14 @@ void USDStageReader::clear_readers() readers_.clear(); } +void USDStageReader::sort_readers() +{ + blender::parallel_sort( + readers_.begin(), readers_.end(), [](const USDPrimReader *a, const USDPrimReader *b) { + const char *na = a ? a->name().c_str() : ""; + const char *nb = b ? b->name().c_str() : ""; + return BLI_strcasecmp(na, nb) < 0; + }); +} + } // Namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_stage.h b/source/blender/io/usd/intern/usd_reader_stage.h index 0ed964c7679..5f4a343f874 100644 --- a/source/blender/io/usd/intern/usd_reader_stage.h +++ b/source/blender/io/usd/intern/usd_reader_stage.h @@ -63,6 +63,8 @@ class USDStageReader { return readers_; }; + void sort_readers(); + private: USDPrimReader *collect_readers(Main *bmain, const pxr::UsdPrim &prim); diff --git a/source/blender/io/usd/intern/usd_writer_mesh.cc b/source/blender/io/usd/intern/usd_writer_mesh.cc index b76f74cfd3d..a39f74c6420 100644 --- a/source/blender/io/usd/intern/usd_writer_mesh.cc +++ b/source/blender/io/usd/intern/usd_writer_mesh.cc @@ -11,6 +11,7 @@ #include "BLI_math_vector.h" #include "BKE_attribute.h" +#include "BKE_attribute.hh" #include "BKE_customdata.h" #include "BKE_lib_id.h" #include "BKE_material.h" @@ -245,8 +246,8 @@ static void get_vertices(const Mesh *mesh, USDMeshData &usd_mesh_data) { usd_mesh_data.points.reserve(mesh->totvert); - const MVert *verts = mesh->mvert; - for (int i = 0; i < mesh->totvert; ++i) { + const Span<MVert> verts = mesh->verts(); + for (const int i : verts.index_range()) { usd_mesh_data.points.push_back(pxr::GfVec3f(verts[i].co)); } } @@ -255,22 +256,27 @@ static void get_loops_polys(const Mesh *mesh, USDMeshData &usd_mesh_data) { /* Only construct face groups (a.k.a. geometry subsets) when we need them for material * assignments. */ - bool construct_face_groups = mesh->totcol > 1; + const bke::AttributeAccessor attributes = mesh->attributes(); + const VArray<int> material_indices = attributes.lookup_or_default<int>( + "material_index", ATTR_DOMAIN_FACE, 0); + if (!material_indices.is_single() && mesh->totcol > 1) { + const VArraySpan<int> indices_span(material_indices); + for (const int i : indices_span.index_range()) { + usd_mesh_data.face_groups[indices_span[i]].push_back(i); + } + } usd_mesh_data.face_vertex_counts.reserve(mesh->totpoly); usd_mesh_data.face_indices.reserve(mesh->totloop); - MLoop *mloop = mesh->mloop; - MPoly *mpoly = mesh->mpoly; - for (int i = 0; i < mesh->totpoly; ++i, ++mpoly) { - MLoop *loop = mloop + mpoly->loopstart; - usd_mesh_data.face_vertex_counts.push_back(mpoly->totloop); - for (int j = 0; j < mpoly->totloop; ++j, ++loop) { - usd_mesh_data.face_indices.push_back(loop->v); - } + const Span<MPoly> polys = mesh->polys(); + const Span<MLoop> loops = mesh->loops(); - if (construct_face_groups) { - usd_mesh_data.face_groups[mpoly->mat_nr].push_back(i); + for (const int i : polys.index_range()) { + const MPoly &poly = polys[i]; + usd_mesh_data.face_vertex_counts.push_back(poly.totloop); + for (const MLoop &loop : loops.slice(poly.loopstart, poly.totloop)) { + usd_mesh_data.face_indices.push_back(loop.v); } } } @@ -279,22 +285,23 @@ static void get_edge_creases(const Mesh *mesh, USDMeshData &usd_mesh_data) { const float factor = 1.0f / 255.0f; - MEdge *edge = mesh->medge; + const Span<MEdge> edges = mesh->edges(); float sharpness; - for (int edge_idx = 0, totedge = mesh->totedge; edge_idx < totedge; ++edge_idx, ++edge) { - if (edge->crease == 0) { + for (const int i : edges.index_range()) { + const MEdge &edge = edges[i]; + if (edge.crease == 0) { continue; } - if (edge->crease == 255) { + if (edge.crease == 255) { sharpness = pxr::UsdGeomMesh::SHARPNESS_INFINITE; } else { - sharpness = static_cast<float>(edge->crease) * factor; + sharpness = static_cast<float>(edge.crease) * factor; } - usd_mesh_data.crease_vertex_indices.push_back(edge->v1); - usd_mesh_data.crease_vertex_indices.push_back(edge->v2); + usd_mesh_data.crease_vertex_indices.push_back(edge.v1); + usd_mesh_data.crease_vertex_indices.push_back(edge.v2); usd_mesh_data.crease_lengths.push_back(2); usd_mesh_data.crease_sharpnesses.push_back(sharpness); } @@ -392,6 +399,8 @@ void USDGenericMeshWriter::write_normals(const Mesh *mesh, pxr::UsdGeomMesh usd_ { pxr::UsdTimeCode timecode = get_export_time_code(); const float(*lnors)[3] = static_cast<float(*)[3]>(CustomData_get_layer(&mesh->ldata, CD_NORMAL)); + const Span<MPoly> polys = mesh->polys(); + const Span<MLoop> loops = mesh->loops(); pxr::VtVec3fArray loop_normals; loop_normals.reserve(mesh->totloop); @@ -406,21 +415,20 @@ void USDGenericMeshWriter::write_normals(const Mesh *mesh, pxr::UsdGeomMesh usd_ /* Compute the loop normals based on the 'smooth' flag. */ const float(*vert_normals)[3] = BKE_mesh_vertex_normals_ensure(mesh); const float(*face_normals)[3] = BKE_mesh_poly_normals_ensure(mesh); - MPoly *mpoly = mesh->mpoly; - for (int poly_idx = 0, totpoly = mesh->totpoly; poly_idx < totpoly; ++poly_idx, ++mpoly) { - MLoop *mloop = mesh->mloop + mpoly->loopstart; + for (const int i : polys.index_range()) { + const MPoly &poly = polys[i]; - if ((mpoly->flag & ME_SMOOTH) == 0) { + if ((poly.flag & ME_SMOOTH) == 0) { /* Flat shaded, use common normal for all verts. */ - pxr::GfVec3f pxr_normal(face_normals[poly_idx]); - for (int loop_idx = 0; loop_idx < mpoly->totloop; ++loop_idx) { + pxr::GfVec3f pxr_normal(face_normals[i]); + for (int loop_idx = 0; loop_idx < poly.totloop; ++loop_idx) { loop_normals.push_back(pxr_normal); } } else { /* Smooth shaded, use individual vert normals. */ - for (int loop_idx = 0; loop_idx < mpoly->totloop; ++loop_idx, ++mloop) { - loop_normals.push_back(pxr::GfVec3f(vert_normals[mloop->v])); + for (const MLoop &loop : loops.slice(poly.loopstart, poly.totloop)) { + loop_normals.push_back(pxr::GfVec3f(vert_normals[loop.v])); } } } diff --git a/source/blender/io/usd/intern/usd_writer_volume.cc b/source/blender/io/usd/intern/usd_writer_volume.cc index 4126be6966a..12db6d73901 100644 --- a/source/blender/io/usd/intern/usd_writer_volume.cc +++ b/source/blender/io/usd/intern/usd_writer_volume.cc @@ -100,7 +100,7 @@ std::optional<std::string> USDVolumeWriter::resolve_vdb_file(const Volume *volum vdb_file_path = construct_vdb_file_path(volume); if (!BKE_volume_save( - volume, usd_export_context_.bmain, NULL, vdb_file_path.value_or("").c_str())) { + volume, usd_export_context_.bmain, nullptr, vdb_file_path.value_or("").c_str())) { return std::nullopt; } } @@ -152,7 +152,7 @@ std::optional<std::string> USDVolumeWriter::construct_vdb_file_path(const Volume strcat(vdb_file_name, ".vdb"); char vdb_file_path[FILE_MAX]; - BLI_path_join(vdb_file_path, sizeof(vdb_file_path), vdb_directory_path, vdb_file_name, NULL); + BLI_path_join(vdb_file_path, sizeof(vdb_file_path), vdb_directory_path, vdb_file_name, nullptr); return vdb_file_path; } diff --git a/source/blender/io/usd/tests/usd_imaging_test.cc b/source/blender/io/usd/tests/usd_imaging_test.cc index 497319c59bd..5cd3c042e59 100644 --- a/source/blender/io/usd/tests/usd_imaging_test.cc +++ b/source/blender/io/usd/tests/usd_imaging_test.cc @@ -42,8 +42,8 @@ TEST_F(USDImagingTest, CapsuleAdapterTest) } pxr::UsdImagingCapsuleAdapter capsule_adapter; - pxr::VtValue points_value = capsule_adapter.GetMeshPoints(capsule.GetPrim(), - pxr::UsdTimeCode::Default()); + pxr::VtValue points_value = pxr::UsdImagingCapsuleAdapter::GetMeshPoints( + capsule.GetPrim(), pxr::UsdTimeCode::Default()); if (!points_value.IsHolding<pxr::VtArray<pxr::GfVec3f>>()) { FAIL() << "Mesh points value holding unexpected type."; return; @@ -52,7 +52,7 @@ TEST_F(USDImagingTest, CapsuleAdapterTest) pxr::VtArray<pxr::GfVec3f> points = points_value.Get<pxr::VtArray<pxr::GfVec3f>>(); EXPECT_FALSE(points.empty()); - pxr::VtValue topology_value = capsule_adapter.GetMeshTopology(); + pxr::VtValue topology_value = pxr::UsdImagingCapsuleAdapter::GetMeshTopology(); if (!topology_value.IsHolding<pxr::HdMeshTopology>()) { FAIL() << "Mesh topology value holding unexpected type."; diff --git a/source/blender/io/wavefront_obj/IO_wavefront_obj.cc b/source/blender/io/wavefront_obj/IO_wavefront_obj.cc index fb0b4a1aca9..2fd2973ee73 100644 --- a/source/blender/io/wavefront_obj/IO_wavefront_obj.cc +++ b/source/blender/io/wavefront_obj/IO_wavefront_obj.cc @@ -4,6 +4,7 @@ * \ingroup obj */ +#include "BLI_path_util.h" #include "BLI_timeit.hh" #include "IO_wavefront_obj.h" @@ -11,14 +12,26 @@ #include "obj_exporter.hh" #include "obj_importer.hh" +using namespace blender::timeit; + +static void report_duration(const char *job, const TimePoint &start_time, const char *path) +{ + Nanoseconds duration = Clock::now() - start_time; + std::cout << "OBJ " << job << " of '" << BLI_path_basename(path) << "' took "; + print_duration(duration); + std::cout << '\n'; +} + void OBJ_export(bContext *C, const OBJExportParams *export_params) { - SCOPED_TIMER("OBJ export"); + TimePoint start_time = Clock::now(); blender::io::obj::exporter_main(C, *export_params); + report_duration("export", start_time, export_params->filepath); } void OBJ_import(bContext *C, const OBJImportParams *import_params) { - SCOPED_TIMER(__func__); + TimePoint start_time = Clock::now(); blender::io::obj::importer_main(C, *import_params); + report_duration("import", start_time, import_params->filepath); } diff --git a/source/blender/io/wavefront_obj/IO_wavefront_obj.h b/source/blender/io/wavefront_obj/IO_wavefront_obj.h index a719dff2126..847b02d3fd1 100644 --- a/source/blender/io/wavefront_obj/IO_wavefront_obj.h +++ b/source/blender/io/wavefront_obj/IO_wavefront_obj.h @@ -72,7 +72,10 @@ struct OBJImportParams { float clamp_size; eIOAxis forward_axis; eIOAxis up_axis; + bool import_vertex_groups; bool validate_meshes; + bool relative_paths; + bool clear_selection; }; /** diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc b/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc index cb95c561547..4d934960010 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc @@ -44,7 +44,7 @@ static const char *DEFORM_GROUP_DISABLED = "off"; * So an empty material name is written. */ static const char *MATERIAL_GROUP_DISABLED = ""; -void OBJWriter::write_vert_uv_normal_indices(FormatHandler<eFileType::OBJ> &fh, +void OBJWriter::write_vert_uv_normal_indices(FormatHandler &fh, const IndexOffsets &offsets, Span<int> vert_indices, Span<int> uv_indices, @@ -57,12 +57,12 @@ void OBJWriter::write_vert_uv_normal_indices(FormatHandler<eFileType::OBJ> &fh, const int uv_offset = offsets.uv_vertex_offset + 1; const int normal_offset = offsets.normal_offset + 1; const int n = vert_indices.size(); - fh.write<eOBJSyntaxElement::poly_element_begin>(); + fh.write_obj_poly_begin(); if (!flip) { for (int j = 0; j < n; ++j) { - fh.write<eOBJSyntaxElement::vertex_uv_normal_indices>(vert_indices[j] + vertex_offset, - uv_indices[j] + uv_offset, - normal_indices[j] + normal_offset); + fh.write_obj_poly_v_uv_normal(vert_indices[j] + vertex_offset, + uv_indices[j] + uv_offset, + normal_indices[j] + normal_offset); } } else { @@ -71,15 +71,15 @@ void OBJWriter::write_vert_uv_normal_indices(FormatHandler<eFileType::OBJ> &fh, * then go backwards. Same logic in other write_*_indices functions below. */ for (int k = 0; k < n; ++k) { int j = k == 0 ? 0 : n - k; - fh.write<eOBJSyntaxElement::vertex_uv_normal_indices>(vert_indices[j] + vertex_offset, - uv_indices[j] + uv_offset, - normal_indices[j] + normal_offset); + fh.write_obj_poly_v_uv_normal(vert_indices[j] + vertex_offset, + uv_indices[j] + uv_offset, + normal_indices[j] + normal_offset); } } - fh.write<eOBJSyntaxElement::poly_element_end>(); + fh.write_obj_poly_end(); } -void OBJWriter::write_vert_normal_indices(FormatHandler<eFileType::OBJ> &fh, +void OBJWriter::write_vert_normal_indices(FormatHandler &fh, const IndexOffsets &offsets, Span<int> vert_indices, Span<int> /*uv_indices*/, @@ -90,24 +90,24 @@ void OBJWriter::write_vert_normal_indices(FormatHandler<eFileType::OBJ> &fh, const int vertex_offset = offsets.vertex_offset + 1; const int normal_offset = offsets.normal_offset + 1; const int n = vert_indices.size(); - fh.write<eOBJSyntaxElement::poly_element_begin>(); + fh.write_obj_poly_begin(); if (!flip) { for (int j = 0; j < n; ++j) { - fh.write<eOBJSyntaxElement::vertex_normal_indices>(vert_indices[j] + vertex_offset, - normal_indices[j] + normal_offset); + fh.write_obj_poly_v_normal(vert_indices[j] + vertex_offset, + normal_indices[j] + normal_offset); } } else { for (int k = 0; k < n; ++k) { int j = k == 0 ? 0 : n - k; - fh.write<eOBJSyntaxElement::vertex_normal_indices>(vert_indices[j] + vertex_offset, - normal_indices[j] + normal_offset); + fh.write_obj_poly_v_normal(vert_indices[j] + vertex_offset, + normal_indices[j] + normal_offset); } } - fh.write<eOBJSyntaxElement::poly_element_end>(); + fh.write_obj_poly_end(); } -void OBJWriter::write_vert_uv_indices(FormatHandler<eFileType::OBJ> &fh, +void OBJWriter::write_vert_uv_indices(FormatHandler &fh, const IndexOffsets &offsets, Span<int> vert_indices, Span<int> uv_indices, @@ -118,24 +118,22 @@ void OBJWriter::write_vert_uv_indices(FormatHandler<eFileType::OBJ> &fh, const int vertex_offset = offsets.vertex_offset + 1; const int uv_offset = offsets.uv_vertex_offset + 1; const int n = vert_indices.size(); - fh.write<eOBJSyntaxElement::poly_element_begin>(); + fh.write_obj_poly_begin(); if (!flip) { for (int j = 0; j < n; ++j) { - fh.write<eOBJSyntaxElement::vertex_uv_indices>(vert_indices[j] + vertex_offset, - uv_indices[j] + uv_offset); + fh.write_obj_poly_v_uv(vert_indices[j] + vertex_offset, uv_indices[j] + uv_offset); } } else { for (int k = 0; k < n; ++k) { int j = k == 0 ? 0 : n - k; - fh.write<eOBJSyntaxElement::vertex_uv_indices>(vert_indices[j] + vertex_offset, - uv_indices[j] + uv_offset); + fh.write_obj_poly_v_uv(vert_indices[j] + vertex_offset, uv_indices[j] + uv_offset); } } - fh.write<eOBJSyntaxElement::poly_element_end>(); + fh.write_obj_poly_end(); } -void OBJWriter::write_vert_indices(FormatHandler<eFileType::OBJ> &fh, +void OBJWriter::write_vert_indices(FormatHandler &fh, const IndexOffsets &offsets, Span<int> vert_indices, Span<int> /*uv_indices*/, @@ -144,27 +142,27 @@ void OBJWriter::write_vert_indices(FormatHandler<eFileType::OBJ> &fh, { const int vertex_offset = offsets.vertex_offset + 1; const int n = vert_indices.size(); - fh.write<eOBJSyntaxElement::poly_element_begin>(); + fh.write_obj_poly_begin(); if (!flip) { for (int j = 0; j < n; ++j) { - fh.write<eOBJSyntaxElement::vertex_indices>(vert_indices[j] + vertex_offset); + fh.write_obj_poly_v(vert_indices[j] + vertex_offset); } } else { for (int k = 0; k < n; ++k) { int j = k == 0 ? 0 : n - k; - fh.write<eOBJSyntaxElement::vertex_indices>(vert_indices[j] + vertex_offset); + fh.write_obj_poly_v(vert_indices[j] + vertex_offset); } } - fh.write<eOBJSyntaxElement::poly_element_end>(); + fh.write_obj_poly_end(); } void OBJWriter::write_header() const { using namespace std::string_literals; - FormatHandler<eFileType::OBJ> fh; - fh.write<eOBJSyntaxElement::string>("# Blender "s + BKE_blender_version_string() + "\n"); - fh.write<eOBJSyntaxElement::string>("# www.blender.org\n"); + FormatHandler fh; + fh.write_string("# Blender "s + BKE_blender_version_string()); + fh.write_string("# www.blender.org"); fh.write_to_file(outfile_); } @@ -174,22 +172,27 @@ void OBJWriter::write_mtllib_name(const StringRefNull mtl_filepath) const 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); - FormatHandler<eFileType::OBJ> fh; - fh.write<eOBJSyntaxElement::mtllib>(mtl_file_name); + FormatHandler fh; + fh.write_obj_mtllib(mtl_file_name); fh.write_to_file(outfile_); } -void OBJWriter::write_object_name(FormatHandler<eFileType::OBJ> &fh, - const OBJMesh &obj_mesh_data) const +static void spaces_to_underscores(std::string &r_name) { - const char *object_name = obj_mesh_data.get_object_name(); + std::replace(r_name.begin(), r_name.end(), ' ', '_'); +} + +void OBJWriter::write_object_name(FormatHandler &fh, const OBJMesh &obj_mesh_data) const +{ + std::string object_name = obj_mesh_data.get_object_name(); + spaces_to_underscores(object_name); if (export_params_.export_object_groups) { - const std::string object_name = obj_mesh_data.get_object_name(); - const char *mesh_name = obj_mesh_data.get_object_mesh_name(); - fh.write<eOBJSyntaxElement::object_group>(object_name + "_" + mesh_name); + std::string mesh_name = obj_mesh_data.get_object_mesh_name(); + spaces_to_underscores(mesh_name); + fh.write_obj_group(object_name + "_" + mesh_name); return; } - fh.write<eOBJSyntaxElement::object_name>(object_name); + fh.write_obj_object(object_name); } /* Split up large meshes into multi-threaded jobs; each job processes @@ -207,9 +210,7 @@ static int calc_chunk_count(int count) * will be written into the final /fh/ buffer at the end. */ template<typename Function> -void obj_parallel_chunked_output(FormatHandler<eFileType::OBJ> &fh, - int tot_count, - const Function &function) +void obj_parallel_chunked_output(FormatHandler &fh, int tot_count, const Function &function) { if (tot_count <= 0) { return; @@ -225,7 +226,7 @@ void obj_parallel_chunked_output(FormatHandler<eFileType::OBJ> &fh, return; } /* Give each chunk its own temporary output buffer, and process them in parallel. */ - std::vector<FormatHandler<eFileType::OBJ>> buffers(chunk_count); + std::vector<FormatHandler> buffers(chunk_count); blender::threading::parallel_for(IndexRange(chunk_count), 1, [&](IndexRange range) { for (const int r : range) { int i_start = r * chunk_size; @@ -242,59 +243,57 @@ void obj_parallel_chunked_output(FormatHandler<eFileType::OBJ> &fh, } } -void OBJWriter::write_vertex_coords(FormatHandler<eFileType::OBJ> &fh, +void OBJWriter::write_vertex_coords(FormatHandler &fh, const OBJMesh &obj_mesh_data, bool write_colors) const { const int tot_count = obj_mesh_data.tot_vertices(); Mesh *mesh = obj_mesh_data.get_mesh(); - CustomDataLayer *colors_layer = nullptr; + const CustomDataLayer *colors_layer = nullptr; if (write_colors) { colors_layer = BKE_id_attributes_active_color_get(&mesh->id); } if (write_colors && (colors_layer != nullptr)) { - MeshComponent component; - component.replace(mesh, GeometryOwnershipType::ReadOnly); - VArray<ColorGeometry4f> attribute = component.attribute_get_for_read<ColorGeometry4f>( + const bke::AttributeAccessor attributes = mesh->attributes(); + const VArray<ColorGeometry4f> attribute = attributes.lookup_or_default<ColorGeometry4f>( colors_layer->name, ATTR_DOMAIN_POINT, {0.0f, 0.0f, 0.0f, 0.0f}); BLI_assert(tot_count == attribute.size()); - obj_parallel_chunked_output(fh, tot_count, [&](FormatHandler<eFileType::OBJ> &buf, int i) { + obj_parallel_chunked_output(fh, tot_count, [&](FormatHandler &buf, int i) { float3 vertex = obj_mesh_data.calc_vertex_coords(i, export_params_.scaling_factor); ColorGeometry4f linear = attribute.get(i); float srgb[3]; linearrgb_to_srgb_v3_v3(srgb, linear); - buf.write<eOBJSyntaxElement::vertex_coords_color>( - vertex[0], vertex[1], vertex[2], srgb[0], srgb[1], srgb[2]); + buf.write_obj_vertex_color(vertex[0], vertex[1], vertex[2], srgb[0], srgb[1], srgb[2]); }); } else { - obj_parallel_chunked_output(fh, tot_count, [&](FormatHandler<eFileType::OBJ> &buf, int i) { + obj_parallel_chunked_output(fh, tot_count, [&](FormatHandler &buf, int i) { float3 vertex = obj_mesh_data.calc_vertex_coords(i, export_params_.scaling_factor); - buf.write<eOBJSyntaxElement::vertex_coords>(vertex[0], vertex[1], vertex[2]); + buf.write_obj_vertex(vertex[0], vertex[1], vertex[2]); }); } } -void OBJWriter::write_uv_coords(FormatHandler<eFileType::OBJ> &fh, OBJMesh &r_obj_mesh_data) const +void OBJWriter::write_uv_coords(FormatHandler &fh, OBJMesh &r_obj_mesh_data) const { const Vector<float2> &uv_coords = r_obj_mesh_data.get_uv_coords(); const int tot_count = uv_coords.size(); - obj_parallel_chunked_output(fh, tot_count, [&](FormatHandler<eFileType::OBJ> &buf, int i) { + obj_parallel_chunked_output(fh, tot_count, [&](FormatHandler &buf, int i) { const float2 &uv_vertex = uv_coords[i]; - buf.write<eOBJSyntaxElement::uv_vertex_coords>(uv_vertex[0], uv_vertex[1]); + buf.write_obj_uv(uv_vertex[0], uv_vertex[1]); }); } -void OBJWriter::write_poly_normals(FormatHandler<eFileType::OBJ> &fh, OBJMesh &obj_mesh_data) +void OBJWriter::write_poly_normals(FormatHandler &fh, OBJMesh &obj_mesh_data) { /* Poly normals should be calculated earlier via store_normal_coords_and_indices. */ const Vector<float3> &normal_coords = obj_mesh_data.get_normal_coords(); const int tot_count = normal_coords.size(); - obj_parallel_chunked_output(fh, tot_count, [&](FormatHandler<eFileType::OBJ> &buf, int i) { + obj_parallel_chunked_output(fh, tot_count, [&](FormatHandler &buf, int i) { const float3 &normal = normal_coords[i]; - buf.write<eOBJSyntaxElement::normal>(normal[0], normal[1], normal[2]); + buf.write_obj_normal(normal[0], normal[1], normal[2]); }); } @@ -329,7 +328,7 @@ static int get_smooth_group(const OBJMesh &mesh, const OBJExportParams ¶ms, return group; } -void OBJWriter::write_poly_elements(FormatHandler<eFileType::OBJ> &fh, +void OBJWriter::write_poly_elements(FormatHandler &fh, const IndexOffsets &offsets, const OBJMesh &obj_mesh_data, std::function<const char *(int)> matname_fn) @@ -341,7 +340,7 @@ void OBJWriter::write_poly_elements(FormatHandler<eFileType::OBJ> &fh, const int tot_deform_groups = obj_mesh_data.tot_deform_groups(); threading::EnumerableThreadSpecific<Vector<float>> group_weights; - obj_parallel_chunked_output(fh, tot_polygons, [&](FormatHandler<eFileType::OBJ> &buf, int idx) { + obj_parallel_chunked_output(fh, tot_polygons, [&](FormatHandler &buf, int idx) { /* Polygon order for writing into the file is not necessarily the same * as order in the mesh; it will be sorted by material indices. Remap current * and previous indices here according to the order. */ @@ -357,7 +356,7 @@ void OBJWriter::write_poly_elements(FormatHandler<eFileType::OBJ> &fh, const int prev_group = get_smooth_group(obj_mesh_data, export_params_, prev_i); const int group = get_smooth_group(obj_mesh_data, export_params_, i); if (group != prev_group) { - buf.write<eOBJSyntaxElement::smooth_group>(group); + buf.write_obj_smooth(group); } } @@ -370,19 +369,22 @@ void OBJWriter::write_poly_elements(FormatHandler<eFileType::OBJ> &fh, prev_i, local_weights); const int16_t group = obj_mesh_data.get_poly_deform_group_index(i, local_weights); if (group != prev_group) { - buf.write<eOBJSyntaxElement::object_group>( - group == NOT_FOUND ? DEFORM_GROUP_DISABLED : - obj_mesh_data.get_poly_deform_group_name(group)); + buf.write_obj_group(group == NOT_FOUND ? DEFORM_GROUP_DISABLED : + obj_mesh_data.get_poly_deform_group_name(group)); } } + const bke::AttributeAccessor attributes = obj_mesh_data.get_mesh()->attributes(); + const VArray<int> material_indices = attributes.lookup_or_default<int>( + "material_index", ATTR_DOMAIN_FACE, 0); + /* Write material name and material group if different from previous. */ if (export_params_.export_materials && obj_mesh_data.tot_materials() > 0) { - const int16_t prev_mat = idx == 0 ? NEGATIVE_INIT : obj_mesh_data.ith_poly_matnr(prev_i); - const int16_t mat = obj_mesh_data.ith_poly_matnr(i); + const int16_t prev_mat = idx == 0 ? NEGATIVE_INIT : std::max(0, material_indices[prev_i]); + const int16_t mat = std::max(0, material_indices[i]); if (mat != prev_mat) { if (mat == NOT_FOUND) { - buf.write<eOBJSyntaxElement::poly_usemtl>(MATERIAL_GROUP_DISABLED); + buf.write_obj_usemtl(MATERIAL_GROUP_DISABLED); } else { const char *mat_name = matname_fn(mat); @@ -390,10 +392,11 @@ void OBJWriter::write_poly_elements(FormatHandler<eFileType::OBJ> &fh, mat_name = MATERIAL_GROUP_DISABLED; } if (export_params_.export_material_groups) { - const std::string object_name = obj_mesh_data.get_object_name(); - fh.write<eOBJSyntaxElement::object_group>(object_name + "_" + mat_name); + std::string object_name = obj_mesh_data.get_object_name(); + spaces_to_underscores(object_name); + fh.write_obj_group(object_name + "_" + mat_name); } - buf.write<eOBJSyntaxElement::poly_usemtl>(mat_name); + buf.write_obj_usemtl(mat_name); } } } @@ -408,7 +411,7 @@ void OBJWriter::write_poly_elements(FormatHandler<eFileType::OBJ> &fh, }); } -void OBJWriter::write_edges_indices(FormatHandler<eFileType::OBJ> &fh, +void OBJWriter::write_edges_indices(FormatHandler &fh, const IndexOffsets &offsets, const OBJMesh &obj_mesh_data) const { @@ -420,13 +423,12 @@ void OBJWriter::write_edges_indices(FormatHandler<eFileType::OBJ> &fh, if (!vertex_indices) { continue; } - fh.write<eOBJSyntaxElement::edge>((*vertex_indices)[0] + offsets.vertex_offset + 1, - (*vertex_indices)[1] + offsets.vertex_offset + 1); + fh.write_obj_edge((*vertex_indices)[0] + offsets.vertex_offset + 1, + (*vertex_indices)[1] + offsets.vertex_offset + 1); } } -void OBJWriter::write_nurbs_curve(FormatHandler<eFileType::OBJ> &fh, - const OBJCurve &obj_nurbs_data) const +void OBJWriter::write_nurbs_curve(FormatHandler &fh, 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++) { @@ -434,15 +436,14 @@ void OBJWriter::write_nurbs_curve(FormatHandler<eFileType::OBJ> &fh, 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); - fh.write<eOBJSyntaxElement::vertex_coords>( - vertex_coords[0], vertex_coords[1], vertex_coords[2]); + fh.write_obj_vertex(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); - fh.write<eOBJSyntaxElement::object_group>(nurbs_name); - fh.write<eOBJSyntaxElement::cstype>(); - fh.write<eOBJSyntaxElement::nurbs_degree>(nurbs_degree); + fh.write_obj_group(nurbs_name); + fh.write_obj_cstype(); + fh.write_obj_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. @@ -451,13 +452,13 @@ void OBJWriter::write_nurbs_curve(FormatHandler<eFileType::OBJ> &fh, * 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); - fh.write<eOBJSyntaxElement::curve_element_begin>(); + fh.write_obj_curve_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. */ - fh.write<eOBJSyntaxElement::vertex_indices>(-((i % total_vertices) + 1)); + fh.write_obj_poly_v(-((i % total_vertices) + 1)); } - fh.write<eOBJSyntaxElement::curve_element_end>(); + fh.write_obj_curve_end(); /** * In `parm u 0 0.1 ..` line:, (total control points + 2) equidistant numbers in the @@ -468,7 +469,7 @@ void OBJWriter::write_nurbs_curve(FormatHandler<eFileType::OBJ> &fh, const short flagsu = obj_nurbs_data.get_nurbs_flagu(spline_idx); const bool cyclic = flagsu & CU_NURB_CYCLIC; const bool endpoint = !cyclic && (flagsu & CU_NURB_ENDPOINT); - fh.write<eOBJSyntaxElement::nurbs_parameter_begin>(); + fh.write_obj_nurbs_parm_begin(); for (int i = 1; i <= total_control_points + 2; i++) { float parm = 1.0f * i / (total_control_points + 2 + 1); if (endpoint) { @@ -479,11 +480,10 @@ void OBJWriter::write_nurbs_curve(FormatHandler<eFileType::OBJ> &fh, parm = 1; } } - fh.write<eOBJSyntaxElement::nurbs_parameters>(parm); + fh.write_obj_nurbs_parm(parm); } - fh.write<eOBJSyntaxElement::nurbs_parameter_end>(); - - fh.write<eOBJSyntaxElement::nurbs_group_end>(); + fh.write_obj_nurbs_parm_end(); + fh.write_obj_nurbs_group_end(); } } @@ -491,6 +491,18 @@ void OBJWriter::write_nurbs_curve(FormatHandler<eFileType::OBJ> &fh, /** \name .MTL writers. * \{ */ +static const char *tex_map_type_to_string[] = { + "map_Kd", + "map_Ks", + "map_Ns", + "map_d", + "map_refl", + "map_Ke", + "map_Bump", +}; +BLI_STATIC_ASSERT(ARRAY_SIZE(tex_map_type_to_string) == (int)MTLTexMapType::Count, + "array size mismatch"); + /** * Convert #float3 to string of space-separated numbers, with no leading or trailing space. * Only to be used in NON-performance-critical code. @@ -531,9 +543,9 @@ void MTLWriter::write_header(const char *blen_filepath) const char *blen_basename = (blen_filepath && blen_filepath[0] != '\0') ? BLI_path_basename(blen_filepath) : "None"; - fmt_handler_.write<eMTLSyntaxElement::string>("# Blender "s + BKE_blender_version_string() + - " MTL File: '" + blen_basename + "'\n"); - fmt_handler_.write<eMTLSyntaxElement::string>("# www.blender.org\n"); + fmt_handler_.write_string("# Blender "s + BKE_blender_version_string() + " MTL File: '" + + blen_basename + "'"); + fmt_handler_.write_string("# www.blender.org"); } StringRefNull MTLWriter::mtl_file_path() const @@ -541,61 +553,57 @@ StringRefNull MTLWriter::mtl_file_path() const return mtl_filepath_; } -void MTLWriter::write_bsdf_properties(const MTLMaterial &mtl_material) +void MTLWriter::write_bsdf_properties(const MTLMaterial &mtl) { - fmt_handler_.write<eMTLSyntaxElement::Ns>(mtl_material.Ns); - fmt_handler_.write<eMTLSyntaxElement::Ka>( - mtl_material.Ka.x, mtl_material.Ka.y, mtl_material.Ka.z); - fmt_handler_.write<eMTLSyntaxElement::Kd>( - mtl_material.Kd.x, mtl_material.Kd.y, mtl_material.Kd.z); - fmt_handler_.write<eMTLSyntaxElement::Ks>( - mtl_material.Ks.x, mtl_material.Ks.y, mtl_material.Ks.z); - fmt_handler_.write<eMTLSyntaxElement::Ke>( - mtl_material.Ke.x, mtl_material.Ke.y, mtl_material.Ke.z); - fmt_handler_.write<eMTLSyntaxElement::Ni>(mtl_material.Ni); - fmt_handler_.write<eMTLSyntaxElement::d>(mtl_material.d); - fmt_handler_.write<eMTLSyntaxElement::illum>(mtl_material.illum); + /* For various material properties, we only capture information + * coming from the texture, or the default value of the socket. + * When the texture is present, do not emit the default value. */ + if (!mtl.tex_map_of_type(MTLTexMapType::Ns).is_valid()) { + fmt_handler_.write_mtl_float("Ns", mtl.Ns); + } + fmt_handler_.write_mtl_float3("Ka", mtl.Ka.x, mtl.Ka.y, mtl.Ka.z); + if (!mtl.tex_map_of_type(MTLTexMapType::Kd).is_valid()) { + fmt_handler_.write_mtl_float3("Kd", mtl.Kd.x, mtl.Kd.y, mtl.Kd.z); + } + if (!mtl.tex_map_of_type(MTLTexMapType::Ks).is_valid()) { + fmt_handler_.write_mtl_float3("Ks", mtl.Ks.x, mtl.Ks.y, mtl.Ks.z); + } + if (!mtl.tex_map_of_type(MTLTexMapType::Ke).is_valid()) { + fmt_handler_.write_mtl_float3("Ke", mtl.Ke.x, mtl.Ke.y, mtl.Ke.z); + } + fmt_handler_.write_mtl_float("Ni", mtl.Ni); + if (!mtl.tex_map_of_type(MTLTexMapType::d).is_valid()) { + fmt_handler_.write_mtl_float("d", mtl.d); + } + fmt_handler_.write_mtl_illum(mtl.illum); } -void MTLWriter::write_texture_map( - const MTLMaterial &mtl_material, - const Map<const eMTLSyntaxElement, tex_map_XX>::Item &texture_map, - const char *blen_filedir, - const char *dest_dir, - ePathReferenceMode path_mode, - Set<std::pair<std::string, std::string>> ©_set) +void MTLWriter::write_texture_map(const MTLMaterial &mtl_material, + MTLTexMapType texture_key, + const MTLTexMap &texture_map, + const char *blen_filedir, + const char *dest_dir, + ePathReferenceMode path_mode, + Set<std::pair<std::string, std::string>> ©_set) { std::string options; /* Option strings should have their own leading spaces. */ - if (texture_map.value.translation != float3{0.0f, 0.0f, 0.0f}) { - options.append(" -o ").append(float3_to_string(texture_map.value.translation)); + if (texture_map.translation != float3{0.0f, 0.0f, 0.0f}) { + options.append(" -o ").append(float3_to_string(texture_map.translation)); } - if (texture_map.value.scale != float3{1.0f, 1.0f, 1.0f}) { - options.append(" -s ").append(float3_to_string(texture_map.value.scale)); + if (texture_map.scale != float3{1.0f, 1.0f, 1.0f}) { + options.append(" -s ").append(float3_to_string(texture_map.scale)); } - if (texture_map.key == eMTLSyntaxElement::map_Bump && mtl_material.map_Bump_strength > 0.0001f) { + if (texture_key == MTLTexMapType::bump && mtl_material.map_Bump_strength > 0.0001f) { options.append(" -bm ").append(std::to_string(mtl_material.map_Bump_strength)); } -#define SYNTAX_DISPATCH(eMTLSyntaxElement) \ - if (texture_map.key == eMTLSyntaxElement) { \ - std::string path = path_reference( \ - texture_map.value.image_path.c_str(), blen_filedir, dest_dir, path_mode, ©_set); \ - /* Always emit forward slashes for cross-platform compatibility. */ \ - std::replace(path.begin(), path.end(), '\\', '/'); \ - fmt_handler_.write<eMTLSyntaxElement>(options, path.c_str()); \ - 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); + std::string path = path_reference( + texture_map.image_path.c_str(), blen_filedir, dest_dir, path_mode, ©_set); + /* Always emit forward slashes for cross-platform compatibility. */ + std::replace(path.begin(), path.end(), '\\', '/'); - BLI_assert(!"This map type was not written to the file."); + fmt_handler_.write_mtl_map(tex_map_type_to_string[(int)texture_key], options, path); } void MTLWriter::write_materials(const char *blen_filepath, @@ -616,14 +624,16 @@ void MTLWriter::write_materials(const char *blen_filepath, [](const MTLMaterial &a, const MTLMaterial &b) { return a.name < b.name; }); Set<std::pair<std::string, std::string>> copy_set; for (const MTLMaterial &mtlmat : mtlmaterials_) { - fmt_handler_.write<eMTLSyntaxElement::string>("\n"); - fmt_handler_.write<eMTLSyntaxElement::newmtl>(mtlmat.name); + fmt_handler_.write_string(""); + fmt_handler_.write_mtl_newmtl(mtlmat.name); write_bsdf_properties(mtlmat); - for (const auto &tex : mtlmat.texture_maps.items()) { - if (tex.value.image_path.empty()) { + for (int key = 0; key < (int)MTLTexMapType::Count; key++) { + const MTLTexMap &tex = mtlmat.texture_maps[key]; + if (!tex.is_valid()) { continue; } - write_texture_map(mtlmat, tex, blen_filedir, dest_dir, path_mode, copy_set); + write_texture_map( + mtlmat, (MTLTexMapType)key, tex, blen_filedir, dest_dir, path_mode, copy_set); } } path_reference_copy(copy_set); diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.hh b/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.hh index 97c23484426..4544037fbc1 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.hh @@ -66,7 +66,7 @@ class OBJWriter : NonMovable, NonCopyable { /** * Write object's name or group. */ - void write_object_name(FormatHandler<eFileType::OBJ> &fh, const OBJMesh &obj_mesh_data) const; + void write_object_name(FormatHandler &fh, const OBJMesh &obj_mesh_data) const; /** * Write file name of Material Library in .OBJ file. */ @@ -74,19 +74,19 @@ class OBJWriter : NonMovable, NonCopyable { /** * Write vertex coordinates for all vertices as "v x y z" or "v x y z r g b". */ - void write_vertex_coords(FormatHandler<eFileType::OBJ> &fh, + void write_vertex_coords(FormatHandler &fh, const OBJMesh &obj_mesh_data, bool write_colors) const; /** * Write UV vertex coordinates for all vertices as `vt u v`. * \note UV indices are stored here, but written with polygons later. */ - void write_uv_coords(FormatHandler<eFileType::OBJ> &fh, OBJMesh &obj_mesh_data) const; + void write_uv_coords(FormatHandler &fh, OBJMesh &obj_mesh_data) const; /** * Write loop normals for smooth-shaded polygons, and polygon normals otherwise, as "vn x y z". * \note Normal indices ares stored here, but written with polygons later. */ - void write_poly_normals(FormatHandler<eFileType::OBJ> &fh, OBJMesh &obj_mesh_data); + void write_poly_normals(FormatHandler &fh, OBJMesh &obj_mesh_data); /** * Write polygon elements with at least vertex indices, and conditionally with UV vertex * indices and polygon normal indices. Also write groups: smooth, vertex, material. @@ -94,23 +94,23 @@ class OBJWriter : NonMovable, NonCopyable { * name used in the .obj file. * \note UV indices were stored while writing UV vertices. */ - void write_poly_elements(FormatHandler<eFileType::OBJ> &fh, + void write_poly_elements(FormatHandler &fh, const IndexOffsets &offsets, const OBJMesh &obj_mesh_data, std::function<const char *(int)> matname_fn); /** * Write loose edges of a mesh as "l v1 v2". */ - void write_edges_indices(FormatHandler<eFileType::OBJ> &fh, + void write_edges_indices(FormatHandler &fh, const IndexOffsets &offsets, const OBJMesh &obj_mesh_data) const; /** * Write a NURBS curve to the .OBJ file in parameter form. */ - void write_nurbs_curve(FormatHandler<eFileType::OBJ> &fh, const OBJCurve &obj_nurbs_data) const; + void write_nurbs_curve(FormatHandler &fh, const OBJCurve &obj_nurbs_data) const; private: - using func_vert_uv_normal_indices = void (OBJWriter::*)(FormatHandler<eFileType::OBJ> &fh, + using func_vert_uv_normal_indices = void (OBJWriter::*)(FormatHandler &fh, const IndexOffsets &offsets, Span<int> vert_indices, Span<int> uv_indices, @@ -124,7 +124,7 @@ class OBJWriter : NonMovable, NonCopyable { /** * Write one line of polygon indices as "f v1/vt1/vn1 v2/vt2/vn2 ...". */ - void write_vert_uv_normal_indices(FormatHandler<eFileType::OBJ> &fh, + void write_vert_uv_normal_indices(FormatHandler &fh, const IndexOffsets &offsets, Span<int> vert_indices, Span<int> uv_indices, @@ -133,7 +133,7 @@ class OBJWriter : NonMovable, NonCopyable { /** * Write one line of polygon indices as "f v1//vn1 v2//vn2 ...". */ - void write_vert_normal_indices(FormatHandler<eFileType::OBJ> &fh, + void write_vert_normal_indices(FormatHandler &fh, const IndexOffsets &offsets, Span<int> vert_indices, Span<int> /*uv_indices*/, @@ -142,7 +142,7 @@ class OBJWriter : NonMovable, NonCopyable { /** * Write one line of polygon indices as "f v1/vt1 v2/vt2 ...". */ - void write_vert_uv_indices(FormatHandler<eFileType::OBJ> &fh, + void write_vert_uv_indices(FormatHandler &fh, const IndexOffsets &offsets, Span<int> vert_indices, Span<int> uv_indices, @@ -151,7 +151,7 @@ class OBJWriter : NonMovable, NonCopyable { /** * Write one line of polygon indices as "f v1 v2 ...". */ - void write_vert_indices(FormatHandler<eFileType::OBJ> &fh, + void write_vert_indices(FormatHandler &fh, const IndexOffsets &offsets, Span<int> vert_indices, Span<int> /*uv_indices*/, @@ -164,7 +164,7 @@ class OBJWriter : NonMovable, NonCopyable { */ class MTLWriter : NonMovable, NonCopyable { private: - FormatHandler<eFileType::MTL> fmt_handler_; + FormatHandler fmt_handler_; FILE *outfile_; std::string mtl_filepath_; Vector<MTLMaterial> mtlmaterials_; @@ -208,7 +208,8 @@ class MTLWriter : NonMovable, NonCopyable { * Write a texture map in the form "map_XX -s 1. 1. 1. -o 0. 0. 0. [-bm 1.] path/to/image". */ void write_texture_map(const MTLMaterial &mtl_material, - const Map<const eMTLSyntaxElement, tex_map_XX>::Item &texture_map, + MTLTexMapType texture_key, + const MTLTexMap &texture_map, const char *blen_filedir, const char *dest_dir, ePathReferenceMode mode, diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_io.hh b/source/blender/io/wavefront_obj/exporter/obj_export_io.hh index 5413c9969e3..59ee7bd32c0 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_io.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_io.hh @@ -23,268 +23,24 @@ namespace blender::io::obj { -enum class eFileType { - OBJ, - MTL, -}; - -enum class eOBJSyntaxElement { - vertex_coords, - vertex_coords_color, - 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; - -/* Used to prevent mixing of say OBJ file format with MTL syntax elements. */ -template<> struct FileTypeTraits<eFileType::OBJ> { - using SyntaxType = eOBJSyntaxElement; -}; - -template<> struct FileTypeTraits<eFileType::MTL> { - using SyntaxType = eMTLSyntaxElement; -}; - -struct FormattingSyntax { - /* Formatting syntax with the file format key like `newmtl %s\n`. */ - const char *fmt = nullptr; - /* Number of arguments needed by the syntax. */ - const int total_args = 0; - /* Whether types of the given arguments are accepted by the syntax above. Fail to compile by - * default. - */ - const bool are_types_valid = false; -}; - -/** - * Type dependent but always false. Use to add a `constexpr` 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>); - -/* GCC (at least 9.3) while compiling the obj_exporter_tests.cc with optimizations on, - * results in "obj_export_io.hh:205:18: warning: ‘%s’ directive output truncated writing 34 bytes - * into a region of size 6" and similar warnings. Yes the output is truncated, and that is covered - * as an edge case by tests on purpose. */ -#if defined(__GNUC__) && !defined(__clang__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wformat-truncation" -#endif -template<typename... T> -constexpr FormattingSyntax syntax_elem_to_formatting(const eOBJSyntaxElement key) -{ - switch (key) { - case eOBJSyntaxElement::vertex_coords: { - return {"v {:.6f} {:.6f} {:.6f}\n", 3, is_type_float<T...>}; - } - case eOBJSyntaxElement::vertex_coords_color: { - return {"v {:.6f} {:.6f} {:.6f} {:.4f} {:.4f} {:.4f}\n", 6, is_type_float<T...>}; - } - case eOBJSyntaxElement::uv_vertex_coords: { - return {"vt {:.6f} {:.6f}\n", 2, is_type_float<T...>}; - } - case eOBJSyntaxElement::normal: { - return {"vn {:.4f} {:.4f} {:.4f}\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 {" {}/{}/{}", 3, is_type_integral<T...>}; - } - case eOBJSyntaxElement::vertex_normal_indices: { - return {" {}//{}", 2, is_type_integral<T...>}; - } - case eOBJSyntaxElement::vertex_uv_indices: { - return {" {}/{}", 2, is_type_integral<T...>}; - } - case eOBJSyntaxElement::vertex_indices: { - return {" {}", 1, is_type_integral<T...>}; - } - case eOBJSyntaxElement::poly_usemtl: { - return {"usemtl {}\n", 1, is_type_string_related<T...>}; - } - case eOBJSyntaxElement::edge: { - return {"l {} {}\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 {}\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 u 0.0", 0, is_type_string_related<T...>}; - } - case eOBJSyntaxElement::nurbs_parameters: { - return {" {:.6f}", 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 {}\n", 1, is_type_string_related<T...>}; - } - case eOBJSyntaxElement::smooth_group: { - return {"s {}\n", 1, is_type_integral<T...>}; - } - case eOBJSyntaxElement::object_group: { - return {"g {}\n", 1, is_type_string_related<T...>}; - } - case eOBJSyntaxElement::object_name: { - return {"o {}\n", 1, is_type_string_related<T...>}; - } - case eOBJSyntaxElement::string: { - return {"{}", 1, is_type_string_related<T...>}; - } - } -} - -template<typename... T> -constexpr FormattingSyntax syntax_elem_to_formatting(const eMTLSyntaxElement key) -{ - switch (key) { - case eMTLSyntaxElement::newmtl: { - return {"newmtl {}\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 {}\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...>}; - } - /* NOTE: first texture map related argument, if present, will have its own leading space. */ - case eMTLSyntaxElement::map_Kd: { - return {"map_Kd{} {}\n", 2, is_type_string_related<T...>}; - } - case eMTLSyntaxElement::map_Ks: { - return {"map_Ks{} {}\n", 2, is_type_string_related<T...>}; - } - case eMTLSyntaxElement::map_Ns: { - return {"map_Ns{} {}\n", 2, is_type_string_related<T...>}; - } - case eMTLSyntaxElement::map_d: { - return {"map_d{} {}\n", 2, is_type_string_related<T...>}; - } - case eMTLSyntaxElement::map_refl: { - return {"map_refl{} {}\n", 2, is_type_string_related<T...>}; - } - case eMTLSyntaxElement::map_Ke: { - return {"map_Ke{} {}\n", 2, is_type_string_related<T...>}; - } - case eMTLSyntaxElement::map_Bump: { - return {"map_Bump{} {}\n", 2, is_type_string_related<T...>}; - } - case eMTLSyntaxElement::string: { - return {"{}", 1, is_type_string_related<T...>}; - } - } -} -#if defined(__GNUC__) && !defined(__clang__) -# pragma GCC diagnostic pop -#endif - /** - * File format and syntax agnostic file buffer writer. + * File buffer writer. * All writes are done into an internal chunked memory buffer * (list of default 64 kilobyte blocks). * Call write_fo_file once in a while to write the memory buffer(s) * into the given file. */ -template<eFileType filetype, size_t buffer_chunk_size = 64 * 1024> class FormatHandler : NonCopyable, NonMovable { private: typedef std::vector<char> VectorChar; std::vector<VectorChar> blocks_; + size_t buffer_chunk_size_; public: + FormatHandler(size_t buffer_chunk_size = 64 * 1024) : buffer_chunk_size_(buffer_chunk_size) + { + } + /* Write contents to the buffer(s) into a file, and clear the buffers. */ void write_to_file(FILE *f) { @@ -305,7 +61,7 @@ class FormatHandler : NonCopyable, NonMovable { return blocks_.size(); } - void append_from(FormatHandler<filetype, buffer_chunk_size> &v) + void append_from(FormatHandler &v) { blocks_.insert(blocks_.end(), std::make_move_iterator(v.blocks_.begin()), @@ -313,24 +69,132 @@ class FormatHandler : NonCopyable, NonMovable { v.blocks_.clear(); } - /** - * Example invocation: `writer->write<eMTLSyntaxElement::newmtl>("foo")`. - * - * \param key: Must match what the instance's filetype expects; i.e., `eMTLSyntaxElement` for - * `eFileType::MTL`. - */ - template<typename FileTypeTraits<filetype>::SyntaxType key, typename... T> - constexpr void write(T &&...args) - { - /* Get format syntax, number of arguments expected and whether types of given arguments are - * valid. - */ - constexpr FormattingSyntax fmt_nargs_valid = syntax_elem_to_formatting<T...>(key); - BLI_STATIC_ASSERT(fmt_nargs_valid.are_types_valid && - (sizeof...(T) == fmt_nargs_valid.total_args), - "Types of all arguments and the number of arguments should match what the " - "formatting specifies."); - write_impl(fmt_nargs_valid.fmt, std::forward<T>(args)...); + void write_obj_vertex(float x, float y, float z) + { + write_impl("v {:.6f} {:.6f} {:.6f}\n", x, y, z); + } + void write_obj_vertex_color(float x, float y, float z, float r, float g, float b) + { + write_impl("v {:.6f} {:.6f} {:.6f} {:.4f} {:.4f} {:.4f}\n", x, y, z, r, g, b); + } + void write_obj_uv(float x, float y) + { + write_impl("vt {:.6f} {:.6f}\n", x, y); + } + void write_obj_normal(float x, float y, float z) + { + write_impl("vn {:.4f} {:.4f} {:.4f}\n", x, y, z); + } + void write_obj_poly_begin() + { + write_impl("f"); + } + void write_obj_poly_end() + { + write_obj_newline(); + } + void write_obj_poly_v_uv_normal(int v, int uv, int n) + { + write_impl(" {}/{}/{}", v, uv, n); + } + void write_obj_poly_v_normal(int v, int n) + { + write_impl(" {}//{}", v, n); + } + void write_obj_poly_v_uv(int v, int uv) + { + write_impl(" {}/{}", v, uv); + } + void write_obj_poly_v(int v) + { + write_impl(" {}", v); + } + void write_obj_usemtl(StringRef s) + { + write_impl("usemtl {}\n", s); + } + void write_obj_mtllib(StringRef s) + { + write_impl("mtllib {}\n", s); + } + void write_obj_smooth(int s) + { + write_impl("s {}\n", s); + } + void write_obj_group(StringRef s) + { + write_impl("g {}\n", s); + } + void write_obj_object(StringRef s) + { + write_impl("o {}\n", s); + } + void write_obj_edge(int a, int b) + { + write_impl("l {} {}\n", a, b); + } + void write_obj_cstype() + { + write_impl("cstype bspline\n"); + } + void write_obj_nurbs_degree(int deg) + { + write_impl("deg {}\n", deg); + } + void write_obj_curve_begin() + { + write_impl("curv 0.0 1.0"); + } + void write_obj_curve_end() + { + write_obj_newline(); + } + void write_obj_nurbs_parm_begin() + { + write_impl("parm u 0.0"); + } + void write_obj_nurbs_parm(float v) + { + write_impl(" {:.6f}", v); + } + void write_obj_nurbs_parm_end() + { + write_impl(" 1.0\n"); + } + void write_obj_nurbs_group_end() + { + write_impl("end\n"); + } + void write_obj_newline() + { + write_impl("\n"); + } + + void write_mtl_newmtl(StringRef s) + { + write_impl("newmtl {}\n", s); + } + void write_mtl_float(const char *type, float v) + { + write_impl("{} {:.6f}\n", type, v); + } + void write_mtl_float3(const char *type, float r, float g, float b) + { + write_impl("{} {:.6f} {:.6f} {:.6f}\n", type, r, g, b); + } + void write_mtl_illum(int mode) + { + write_impl("illum {}\n", mode); + } + /* NOTE: options, if present, will have its own leading space. */ + void write_mtl_map(const char *type, StringRef options, StringRef value) + { + write_impl("{}{} {}\n", type, options, value); + } + + void write_string(StringRef s) + { + write_impl("{}\n", s); } private: @@ -340,7 +204,7 @@ class FormatHandler : NonCopyable, NonMovable { { if (blocks_.empty() || (blocks_.back().capacity() - blocks_.back().size() < at_least)) { VectorChar &b = blocks_.emplace_back(VectorChar()); - b.reserve(std::max(at_least, buffer_chunk_size)); + b.reserve(std::max(at_least, buffer_chunk_size_)); } } diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc index e2ecda32717..10880b016fb 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc @@ -6,6 +6,7 @@ /* Silence warnings from copying deprecated fields. Needed for an Object copy constructor use. */ #define DNA_DEPRECATED_ALLOW +#include "BKE_attribute.hh" #include "BKE_customdata.h" #include "BKE_deform.h" #include "BKE_lib_id.h" @@ -47,7 +48,7 @@ OBJMesh::OBJMesh(Depsgraph *depsgraph, const OBJExportParams &export_params, Obj /* 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)) { + if (export_params.export_triangulated_mesh && export_object_eval_.type == OB_MESH) { std::tie(export_mesh_eval_, mesh_eval_needs_free_) = triangulate_mesh_eval(); } set_world_axes_transform(export_params.forward_axis, export_params.up_axis); @@ -133,7 +134,7 @@ void OBJMesh::set_world_axes_transform(const eIOAxis forward, const eIOAxis up) copy_m3_m4(normal_matrix, world_and_axes_transform_); invert_m3_m3(world_and_axes_normal_transform_, normal_matrix); transpose_m3(world_and_axes_normal_transform_); - mirrored_transform_ = determinant_m3_array(world_and_axes_normal_transform_) < 0; + mirrored_transform_ = is_negative_m3(world_and_axes_normal_transform_); } int OBJMesh::tot_vertices() const @@ -187,28 +188,38 @@ void OBJMesh::ensure_mesh_edges() const 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, + const Span<MEdge> edges = export_mesh_eval_->edges(); + const Span<MPoly> polys = export_mesh_eval_->polys(); + const Span<MLoop> loops = export_mesh_eval_->loops(); + poly_smooth_groups_ = BKE_mesh_calc_smoothgroups(edges.data(), + edges.size(), + polys.data(), + polys.size(), + loops.data(), + loops.size(), &tot_smooth_groups_, use_bitflags); } void OBJMesh::calc_poly_order() { - const int tot_polys = tot_polygons(); - poly_order_.resize(tot_polys); - for (int i = 0; i < tot_polys; ++i) { + const bke::AttributeAccessor attributes = export_mesh_eval_->attributes(); + const VArray<int> material_indices = attributes.lookup_or_default<int>( + "material_index", ATTR_DOMAIN_FACE, 0); + if (material_indices.is_single() && material_indices.get_internal_single() == 0) { + return; + } + const VArraySpan<int> material_indices_span(material_indices); + + poly_order_.resize(material_indices_span.size()); + for (const int i : material_indices_span.index_range()) { poly_order_[i] = i; } - const MPoly *mpolys = export_mesh_eval_->mpoly; + /* Sort polygons by their material index. */ blender::parallel_sort(poly_order_.begin(), poly_order_.end(), [&](int a, int b) { - int mat_a = mpolys[a].mat_nr; - int mat_b = mpolys[b].mat_nr; + int mat_a = material_indices_span[a]; + int mat_b = material_indices_span[b]; if (mat_a != mat_b) { return mat_a < mat_b; } @@ -219,26 +230,20 @@ void OBJMesh::calc_poly_order() const Material *OBJMesh::get_object_material(const int16_t mat_nr) const { /** - * The const_cast is safe here because BKE_object_material_get won't change the object + * The const_cast is safe here because #BKE_object_material_get_eval won't change the object * but it is a big can of worms to fix the declaration of that function right now. * * The call uses "+ 1" as material getter needs one-based indices. */ Object *obj = const_cast<Object *>(&export_object_eval_); - const Material *r_mat = BKE_object_material_get(obj, mat_nr + 1); + const Material *r_mat = BKE_object_material_get_eval(obj, mat_nr + 1); return r_mat; } bool OBJMesh::is_ith_poly_smooth(const int poly_index) const { - return export_mesh_eval_->mpoly[poly_index].flag & ME_SMOOTH; -} - -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; + const Span<MPoly> polys = export_mesh_eval_->polys(); + return polys[poly_index].flag & ME_SMOOTH; } const char *OBJMesh::get_object_name() const @@ -263,7 +268,8 @@ const char *OBJMesh::get_object_material_name(const int16_t mat_nr) const 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); + const Span<MVert> verts = export_mesh_eval_->verts(); + copy_v3_v3(r_coords, verts[vert_index].co); mul_m4_v3(world_and_axes_transform_, r_coords); mul_v3_fl(r_coords, scaling_factor); return r_coords; @@ -271,8 +277,10 @@ float3 OBJMesh::calc_vertex_coords(const int vert_index, const float scaling_fac 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 Span<MPoly> polys = export_mesh_eval_->polys(); + const Span<MLoop> loops = export_mesh_eval_->loops(); + const MPoly &mpoly = polys[poly_index]; + const MLoop *mloop = &loops[mpoly.loopstart]; const int totloop = mpoly.totloop; Vector<int> r_poly_vertex_indices(totloop); for (int loop_index = 0; loop_index < totloop; loop_index++) { @@ -283,9 +291,8 @@ Vector<int> OBJMesh::calc_poly_vertex_indices(const int poly_index) const void OBJMesh::store_uv_coords_and_indices() { - const MPoly *mpoly = export_mesh_eval_->mpoly; - const MLoop *mloop = export_mesh_eval_->mloop; - const int totpoly = export_mesh_eval_->totpoly; + const Span<MPoly> polys = export_mesh_eval_->polys(); + const Span<MLoop> loops = export_mesh_eval_->loops(); const int totvert = export_mesh_eval_->totvert; const MLoopUV *mloopuv = static_cast<const MLoopUV *>( CustomData_get_layer(&export_mesh_eval_->ldata, CD_MLOOPUV)); @@ -296,9 +303,9 @@ void OBJMesh::store_uv_coords_and_indices() 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); + polys.data(), nullptr, loops.data(), mloopuv, polys.size(), totvert, limit, false, false); - uv_indices_.resize(totpoly); + uv_indices_.resize(polys.size()); /* At least total vertices of a mesh will be present in its texture map. So * reserve minimum space early. */ uv_coords_.reserve(totvert); @@ -310,16 +317,16 @@ void OBJMesh::store_uv_coords_and_indices() if (uv_vert->separate) { tot_uv_vertices_ += 1; } - const int vertices_in_poly = mpoly[uv_vert->poly_index].totloop; + const int verts_in_poly = polys[uv_vert->poly_index].totloop; /* Store UV vertex coordinates. */ uv_coords_.resize(tot_uv_vertices_); - const int loopstart = mpoly[uv_vert->poly_index].loopstart; + const int loopstart = polys[uv_vert->poly_index].loopstart; Span<float> vert_uv_coords(mloopuv[loopstart + uv_vert->loop_of_poly_index].uv, 2); uv_coords_[tot_uv_vertices_ - 1] = float2(vert_uv_coords[0], vert_uv_coords[1]); /* Store UV vertex indices. */ - uv_indices_[uv_vert->poly_index].resize(vertices_in_poly); + uv_indices_[uv_vert->poly_index].resize(verts_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; } @@ -340,10 +347,11 @@ Span<int> OBJMesh::calc_poly_uv_indices(const int poly_index) const 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); + const Span<MVert> verts = export_mesh_eval_->verts(); + const Span<MPoly> polys = export_mesh_eval_->polys(); + const Span<MLoop> loops = export_mesh_eval_->loops(); + const MPoly &poly = polys[poly_index]; + BKE_mesh_calc_poly_normal(&poly, &loops[poly.loopstart], verts.data(), r_poly_normal); mul_m3_v3(world_and_axes_normal_transform_, r_poly_normal); normalize_v3(r_poly_normal); return r_poly_normal; @@ -367,6 +375,8 @@ static float3 round_float3_to_n_digits(const float3 &v, int round_digits) void OBJMesh::store_normal_coords_and_indices() { + const Span<MPoly> polys = export_mesh_eval_->polys(); + /* We'll round normal components to 4 digits. * This will cover up some minor differences * between floating point calculations on different platforms. @@ -382,7 +392,7 @@ void OBJMesh::store_normal_coords_and_indices() const float(*lnors)[3] = static_cast<const float(*)[3]>( CustomData_get_layer(&export_mesh_eval_->ldata, CD_NORMAL)); for (int poly_index = 0; poly_index < export_mesh_eval_->totpoly; ++poly_index) { - const MPoly &mpoly = export_mesh_eval_->mpoly[poly_index]; + const MPoly &mpoly = polys[poly_index]; bool need_per_loop_normals = lnors != nullptr || (mpoly.flag & ME_SMOOTH); if (need_per_loop_normals) { for (int loop_of_poly = 0; loop_of_poly < mpoly.totloop; ++loop_of_poly) { @@ -426,7 +436,8 @@ Vector<int> OBJMesh::calc_poly_normal_indices(const int poly_index) const if (loop_to_normal_index_.is_empty()) { return {}; } - const MPoly &mpoly = export_mesh_eval_->mpoly[poly_index]; + const Span<MPoly> polys = export_mesh_eval_->polys(); + const MPoly &mpoly = polys[poly_index]; const int totloop = mpoly.totloop; Vector<int> r_poly_normal_indices(totloop); for (int poly_loop_index = 0; poly_loop_index < totloop; poly_loop_index++) { @@ -449,23 +460,23 @@ int16_t OBJMesh::get_poly_deform_group_index(const int poly_index, { BLI_assert(poly_index < export_mesh_eval_->totpoly); BLI_assert(group_weights.size() == BKE_object_defgroup_count(&export_object_eval_)); - - const MDeformVert *dvert_layer = static_cast<const MDeformVert *>( - CustomData_get_layer(&export_mesh_eval_->vdata, CD_MDEFORMVERT)); - if (!dvert_layer) { + const Span<MPoly> polys = export_mesh_eval_->polys(); + const Span<MLoop> loops = export_mesh_eval_->loops(); + const Span<MDeformVert> dverts = export_mesh_eval_->deform_verts(); + if (dverts.is_empty()) { return NOT_FOUND; } group_weights.fill(0); bool found_any_group = false; - const MPoly &mpoly = export_mesh_eval_->mpoly[poly_index]; - const MLoop *mloop = &export_mesh_eval_->mloop[mpoly.loopstart]; + const MPoly &mpoly = polys[poly_index]; + const MLoop *mloop = &loops[mpoly.loopstart]; for (int loop_i = 0; loop_i < mpoly.totloop; ++loop_i, ++mloop) { - const MDeformVert &dvert = dvert_layer[mloop->v]; - for (int weight_i = 0; weight_i < dvert.totweight; ++weight_i) { - const auto group = dvert.dw[weight_i].def_nr; + const MDeformVert &dv = dverts[mloop->v]; + for (int weight_i = 0; weight_i < dv.totweight; ++weight_i) { + const auto group = dv.dw[weight_i].def_nr; if (group < group_weights.size()) { - group_weights[group] += dvert.dw[weight_i].weight; + group_weights[group] += dv.dw[weight_i].weight; found_any_group = true; } } @@ -489,7 +500,8 @@ const char *OBJMesh::get_poly_deform_group_name(const int16_t def_group_index) c 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]; + const Span<MEdge> edges = export_mesh_eval_->edges(); + const MEdge &edge = edges[edge_index]; if (edge.flag & ME_LOOSEEDGE) { return std::array<int, 2>{static_cast<int>(edge.v1), static_cast<int>(edge.v2)}; } diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh index ee2e6227700..db29f5651ed 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh @@ -130,11 +130,6 @@ class OBJMesh : NonCopyable { * Return mat_nr-th material of the object. The given index should be zero-based. */ const Material *get_object_material(int16_t mat_nr) const; - /** - * Returns a zero-based index of a polygon's material indexing into - * the Object's material slots. - */ - int16_t ith_poly_matnr(int poly_index) const; void ensure_mesh_normals() const; void ensure_mesh_edges() const; diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc index 4ed148ec64e..6a02695c304 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc @@ -6,6 +6,7 @@ #include "BKE_image.h" #include "BKE_node.h" +#include "BKE_node_runtime.hh" #include "BLI_map.hh" #include "BLI_math_vector.h" @@ -15,13 +16,23 @@ #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 { +const char *tex_map_type_to_socket_id[] = { + "Base Color", + "Specular", + "Roughness", + "Alpha", + "Metallic", + "Emission", + "Normal", +}; +BLI_STATIC_ASSERT(ARRAY_SIZE(tex_map_type_to_socket_id) == (int)MTLTexMapType::Count, + "array size mismatch"); + /** * Copy a float property of the given type from the bNode to given buffer. */ @@ -72,25 +83,25 @@ static void copy_property_from_node(const eNodeSocketDatatype property_type, * 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) + const bNodeTree &node_tree, + const char *dest_socket_id, + Vector<const bNodeSocket *> &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())) { + Span<const bNode *> object_dest_nodes = node_tree.nodes_by_type(dest_node->idname); + Span<const bNodeSocket *> dest_inputs = object_dest_nodes.first()->input_sockets(); + const bNodeSocket *dest_socket = nullptr; + for (const bNodeSocket *curr_socket : dest_inputs) { + if (STREQ(curr_socket->identifier, dest_socket_id)) { dest_socket = curr_socket; break; } } if (dest_socket) { - Span<const nodes::OutputSocketRef *> linked_sockets = dest_socket->directly_linked_sockets(); + Span<const bNodeSocket *> linked_sockets = dest_socket->directly_linked_sockets(); r_linked_sockets.resize(linked_sockets.size()); r_linked_sockets = linked_sockets; } @@ -99,40 +110,52 @@ static void linked_sockets_to_dest_id(const bNode *dest_node, /** * 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) +static const bNode *get_node_of_type(Span<const bNodeSocket *> 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; + for (const bNodeSocket *socket : sockets_list) { + const bNode &parent_node = socket->owner_node(); + if (parent_node.typeinfo->type == node_type) { + return &parent_node; } } return nullptr; } -/** +/* * From a texture image shader node, get the image's filepath. * If packed image is found, only the file "name" is returned. */ -static const char *get_image_filepath(const bNode *tex_node) +static std::string get_image_filepath(const bNode *tex_node) { if (!tex_node) { - return nullptr; + return ""; } Image *tex_image = reinterpret_cast<Image *>(tex_node->id); if (!tex_image || !BKE_image_has_filepath(tex_image)) { - return nullptr; + return ""; } - 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; + const char *filename = BLI_path_slash_rfind(tex_image->filepath) + 1; fprintf(stderr, "Packed image found:'%s'. Unpack and place the image in the same " "directory as the .MTL file.\n", - path); + filename); + return filename; } + + char path[FILE_MAX]; + BLI_strncpy(path, tex_image->filepath, FILE_MAX); + + if (tex_image->source == IMA_SRC_SEQUENCE) { + char head[FILE_MAX], tail[FILE_MAX]; + unsigned short numlen; + int framenr = static_cast<NodeTexImage *>(tex_node->storage)->iuser.framenr; + BLI_path_sequence_decode(path, head, tail, &numlen); + BLI_path_sequence_encode(path, head, tail, numlen, framenr); + } + return path; } @@ -141,16 +164,16 @@ static const char *get_image_filepath(const bNode *tex_node) * 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) +static const bNode *find_bsdf_node(const bNodeTree *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) { + for (const bNode *node : nodetree->nodes_by_type("ShaderNodeOutputMaterial")) { + const bNodeSocket &node_input_socket0 = node->input_socket(0); + for (const bNodeSocket *out_sock : node_input_socket0.directly_linked_sockets()) { + const bNode &in_node = out_sock->owner_node(); + if (in_node.typeinfo->type == SH_NODE_BSDF_PRINCIPLED) { return &in_node; } } @@ -161,55 +184,50 @@ static const nodes::NodeRef *find_bsdf_node(const nodes::NodeTreeRef *nodetree) /** * Store properties found either in bNode or material into r_mtl_mat. */ -static void store_bsdf_properties(const nodes::NodeRef *bsdf_node, +static void store_bsdf_properties(const bNode *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}); + if (bsdf_node) { + copy_property_from_node(SOCK_FLOAT, bsdf_node, "Roughness", {&roughness, 1}); } /* Empirical approximation. Importer should use the inverse of this method. */ float spec_exponent = (1.0f - roughness); spec_exponent *= spec_exponent * 1000.0f; float specular = material->spec; - if (bnode) { - copy_property_from_node(SOCK_FLOAT, bnode, "Specular", {&specular, 1}); + if (bsdf_node) { + copy_property_from_node(SOCK_FLOAT, bsdf_node, "Specular", {&specular, 1}); } float metallic = material->metallic; - if (bnode) { - copy_property_from_node(SOCK_FLOAT, bnode, "Metallic", {&metallic, 1}); + if (bsdf_node) { + copy_property_from_node(SOCK_FLOAT, bsdf_node, "Metallic", {&metallic, 1}); } float refraction_index = 1.0f; - if (bnode) { - copy_property_from_node(SOCK_FLOAT, bnode, "IOR", {&refraction_index, 1}); + if (bsdf_node) { + copy_property_from_node(SOCK_FLOAT, bsdf_node, "IOR", {&refraction_index, 1}); } float dissolved = material->a; - if (bnode) { - copy_property_from_node(SOCK_FLOAT, bnode, "Alpha", {&dissolved, 1}); + if (bsdf_node) { + copy_property_from_node(SOCK_FLOAT, bsdf_node, "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}); + if (bsdf_node) { + copy_property_from_node(SOCK_RGBA, bsdf_node, "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}); + if (bsdf_node) { + copy_property_from_node(SOCK_FLOAT, bsdf_node, "Emission Strength", {&emission_strength, 1}); + copy_property_from_node(SOCK_RGBA, bsdf_node, "Emission", {emission_col, 3}); } mul_v3_fl(emission_col, emission_strength); @@ -253,8 +271,8 @@ static void store_bsdf_properties(const nodes::NodeRef *bsdf_node, /** * Store image texture options and file-paths in `r_mtl_mat`. */ -static void store_image_textures(const nodes::NodeRef *bsdf_node, - const nodes::NodeTreeRef *node_tree, +static void store_image_textures(const bNode *bsdf_node, + const bNodeTree *node_tree, const Material *material, MTLMaterial &r_mtl_mat) { @@ -262,21 +280,20 @@ static void store_image_textures(const nodes::NodeRef *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; + for (int key = 0; key < (int)MTLTexMapType::Count; ++key) { + MTLTexMap &value = r_mtl_mat.texture_maps[key]; + Vector<const bNodeSocket *> linked_sockets; const bNode *normal_map_node{nullptr}; - if (texture_map.key == eMTLSyntaxElement::map_Bump) { + if (key == (int)MTLTexMapType::bump) { /* Find sockets linked to destination "Normal" socket in P-BSDF node. */ - linked_sockets_to_dest_id(bnode, *node_tree, "Normal", linked_sockets); + linked_sockets_to_dest_id(bsdf_node, *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); @@ -285,16 +302,17 @@ static void store_image_textures(const nodes::NodeRef *bsdf_node, } else { /* Skip emission map if emission strength is zero. */ - if (texture_map.key == eMTLSyntaxElement::map_Ke) { + if (key == (int)MTLTexMapType::Ke) { float emission_strength = 0.0f; - copy_property_from_node(SOCK_FLOAT, bnode, "Emission Strength", {&emission_strength, 1}); + copy_property_from_node( + SOCK_FLOAT, bsdf_node, "Emission Strength", {&emission_strength, 1}); if (emission_strength == 0.0f) { continue; } } /* 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); + bsdf_node, *node_tree, tex_map_type_to_socket_id[key], linked_sockets); } /* Among the linked sockets, find Image Texture shader node. */ @@ -302,8 +320,8 @@ static void store_image_textures(const nodes::NodeRef *bsdf_node, if (!tex_node) { continue; } - const char *tex_image_filepath = get_image_filepath(tex_node); - if (!tex_image_filepath) { + const std::string tex_image_filepath = get_image_filepath(tex_node); + if (tex_image_filepath.empty()) { continue; } @@ -317,10 +335,10 @@ static void store_image_textures(const nodes::NodeRef *bsdf_node, } /* 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}); + copy_property_from_node(SOCK_VECTOR, mapping, "Location", {value.translation, 3}); + copy_property_from_node(SOCK_VECTOR, mapping, "Scale", {value.scale, 3}); - texture_map.value.image_path = tex_image_filepath; + value.image_path = tex_image_filepath; } } @@ -330,14 +348,14 @@ MTLMaterial mtlmaterial_for_material(const Material *material) 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 bNodeTree *nodetree = material->nodetree; + if (nodetree != nullptr) { + nodetree->ensure_topology_cache(); } - const nodes::NodeRef *bsdf_node = find_bsdf_node(nodetree); + + const bNode *bsdf_node = find_bsdf_node(nodetree); store_bsdf_properties(bsdf_node, material, mtlmat); store_image_textures(bsdf_node, nodetree, material, mtlmat); - delete nodetree; return mtlmat; } diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh index 80e3127f69f..d8eafff107b 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh @@ -6,38 +6,26 @@ #pragma once -#include "BLI_map.hh" #include "BLI_math_vec_types.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 +struct Material; 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){}; +enum class MTLTexMapType { Kd = 0, Ks, Ns, d, refl, Ke, bump, Count }; +extern const char *tex_map_type_to_socket_id[]; - /** Target socket which this texture node connects to. */ - const std::string dest_socket_id; +struct MTLTexMap { + bool is_valid() const + { + return !image_path.empty(); + } + + /* Target socket which this texture node connects to. */ float3 translation{0.0f}; float3 scale{1.0f}; - /* Only Flat and Smooth projections are supported. */ + /* Only Flat and Sphere projections are supported. */ int projection_type = SHD_PROJ_FLAT; std::string image_path; std::string mtl_dir_path; @@ -47,27 +35,13 @@ struct tex_map_XX { * Container suited for storing Material data for/from a .MTL file. */ struct MTLMaterial { - MTLMaterial() + const MTLTexMap &tex_map_of_type(MTLTexMapType key) const { - 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")); + return texture_maps[(int)key]; } - - /** - * 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) + MTLTexMap &tex_map_of_type(MTLTexMapType key) { - { - BLI_assert(texture_maps.contains_as(key)); - return texture_maps.lookup_as(key); - } + return texture_maps[(int)key]; } std::string name; @@ -81,7 +55,7 @@ struct MTLMaterial { float Ni{-1.0f}; float d{-1.0f}; int illum{-1}; - Map<const eMTLSyntaxElement, tex_map_XX> texture_maps; + MTLTexMap texture_maps[(int)MTLTexMapType::Count]; /** Only used for Normal Map node: "map_Bump". */ float map_Bump_strength{-1.0f}; }; diff --git a/source/blender/io/wavefront_obj/exporter/obj_exporter.cc b/source/blender/io/wavefront_obj/exporter/obj_exporter.cc index b0938084efb..294ea81fd58 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_exporter.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_exporter.cc @@ -143,7 +143,7 @@ static void write_mesh_objects(Vector<std::unique_ptr<OBJMesh>> exportable_as_me * we have to have the output text buffer for each object, * and write them all into the file at the end. */ size_t count = exportable_as_mesh.size(); - std::vector<FormatHandler<eFileType::OBJ>> buffers(count); + std::vector<FormatHandler> buffers(count); /* Serial: gather material indices, ensure normals & edges. */ Vector<Vector<int>> mtlindices; @@ -242,7 +242,7 @@ static void write_mesh_objects(Vector<std::unique_ptr<OBJMesh>> exportable_as_me static void write_nurbs_curve_objects(const Vector<std::unique_ptr<OBJCurve>> &exportable_as_nurbs, const OBJWriter &obj_writer) { - FormatHandler<eFileType::OBJ> fh; + FormatHandler fh; /* #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) { @@ -268,7 +268,7 @@ void export_frame(Depsgraph *depsgraph, const OBJExportParams &export_params, co std::unique_ptr<MTLWriter> mtl_writer = nullptr; if (export_params.export_materials) { try { - mtl_writer = std::make_unique<MTLWriter>(export_params.filepath); + mtl_writer = std::make_unique<MTLWriter>(filepath); } catch (const std::system_error &ex) { print_exception_error(ex); @@ -323,7 +323,7 @@ void exporter_main(bContext *C, const OBJExportParams &export_params) char filepath_with_frames[FILE_MAX]; /* Used to reset the Scene to its original state. */ - const int original_frame = CFRA; + const int original_frame = scene->r.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); @@ -332,11 +332,11 @@ void exporter_main(bContext *C, const OBJExportParams &export_params) return; } - CFRA = frame; + scene->r.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; + scene->r.cfra = original_frame; } } // namespace blender::io::obj 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 ee55dd1e45a..2ad8a09bd90 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 @@ -6,12 +6,15 @@ #include "BLI_map.hh" #include "BLI_math_color.h" +#include "BLI_math_vector.h" #include "BLI_string_ref.hh" #include "BLI_vector.hh" +#include "obj_export_mtl.hh" #include "obj_import_file_reader.hh" #include "obj_import_string_utils.hh" +#include <algorithm> #include <charconv> namespace blender::io::obj { @@ -21,32 +24,24 @@ 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) + Vector<std::unique_ptr<Geometry>> &r_all_geometries) { auto new_geometry = [&]() { r_all_geometries.append(std::make_unique<Geometry>()); Geometry *g = r_all_geometries.last().get(); g->geom_type_ = new_type; g->geometry_name_ = name.is_empty() ? "New object" : name; - g->vertex_start_ = global_vertices.vertices.size(); - g->vertex_color_start_ = global_vertices.vertex_colors.size(); - r_offset.set_index_offset(g->vertex_start_); return g; }; if (prev_geometry && prev_geometry->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 (basically anything but the vertex positions). */ - if (!prev_geometry->face_elements_.is_empty() || prev_geometry->has_vertex_normals_ || - !prev_geometry->edges_.is_empty()) { + * file that indicates that it is a mesh (faces or edges). */ + if (!prev_geometry->face_elements_.is_empty() || !prev_geometry->edges_.is_empty()) { return new_geometry(); } if (new_type == GEOM_MESH) { @@ -69,15 +64,11 @@ static Geometry *create_geometry(Geometry *const prev_geometry, return new_geometry(); } -static void geom_add_vertex(Geometry *geom, - const char *p, - const char *end, - GlobalVertices &r_global_vertices) +static void geom_add_vertex(const char *p, const char *end, GlobalVertices &r_global_vertices) { float3 vert; p = parse_floats(p, end, 0.0f, vert, 3); r_global_vertices.vertices.append(vert); - geom->vertex_count_++; /* OBJ extension: `xyzrgb` vertex colors, when the vertex position * is followed by 3 more RGB color components. See * http://paulbourke.net/dataformats/obj/colour.html */ @@ -87,16 +78,22 @@ static void geom_add_vertex(Geometry *geom, if (srgb.x >= 0 && srgb.y >= 0 && srgb.z >= 0) { float3 linear; srgb_to_linearrgb_v3_v3(linear, srgb); - r_global_vertices.vertex_colors.append(linear); - geom->vertex_color_count_++; + + auto &blocks = r_global_vertices.vertex_colors; + /* If we don't have vertex colors yet, or the previous vertex + * was without color, we need to start a new vertex colors block. */ + if (blocks.is_empty() || (blocks.last().start_vertex_index + blocks.last().colors.size() != + r_global_vertices.vertices.size() - 1)) { + GlobalVertices::VertexColorsBlock block; + block.start_vertex_index = r_global_vertices.vertices.size() - 1; + blocks.append(block); + } + blocks.last().colors.append(linear); } } } -static void geom_add_mrgb_colors(Geometry *geom, - const char *p, - const char *end, - GlobalVertices &r_global_vertices) +static void geom_add_mrgb_colors(const char *p, const char *end, GlobalVertices &r_global_vertices) { /* MRGB color extension, in the form of * "#MRGB MMRRGGBBMMRRGGBB ..." @@ -116,21 +113,36 @@ static void geom_add_mrgb_colors(Geometry *geom, srgb[3] = 0xFF; float linear[4]; srgb_to_linearrgb_uchar4(linear, srgb); - r_global_vertices.vertex_colors.append({linear[0], linear[1], linear[2]}); - geom->vertex_color_count_++; + + auto &blocks = r_global_vertices.vertex_colors; + /* If we don't have vertex colors yet, or the previous vertex + * was without color, we need to start a new vertex colors block. */ + if (blocks.is_empty() || (blocks.last().start_vertex_index + blocks.last().colors.size() != + r_global_vertices.vertices.size())) { + GlobalVertices::VertexColorsBlock block; + block.start_vertex_index = r_global_vertices.vertices.size(); + blocks.append(block); + } + blocks.last().colors.append({linear[0], linear[1], linear[2]}); + /* MRGB colors are specified after vertex positions; each new color + * "pushes" the vertex colors block further back into which vertices it is for. */ + blocks.last().start_vertex_index--; + p += mrgb_length; } } -static void geom_add_vertex_normal(Geometry *geom, - const char *p, +static void geom_add_vertex_normal(const char *p, const char *end, GlobalVertices &r_global_vertices) { float3 normal; parse_floats(p, end, 0.0f, normal, 3); + /* Normals can be printed with only several digits in the file, + * making them ever-so-slightly non unit length. Make sure they are + * normalized. */ + normalize_v3(normal); r_global_vertices.vertex_normals.append(normal); - geom->has_vertex_normals_ = true; } static void geom_add_uv_vertex(const char *p, const char *end, GlobalVertices &r_global_vertices) @@ -143,24 +155,24 @@ static void geom_add_uv_vertex(const char *p, const char *end, GlobalVertices &r static void geom_add_edge(Geometry *geom, const char *p, const char *end, - const VertexIndexOffset &offsets, GlobalVertices &r_global_vertices) { int edge_v1, edge_v2; p = parse_int(p, end, -1, edge_v1); p = parse_int(p, end, -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; + edge_v1 += edge_v1 < 0 ? r_global_vertices.vertices.size() : -1; + edge_v2 += edge_v2 < 0 ? r_global_vertices.vertices.size() : -1; BLI_assert(edge_v1 >= 0 && edge_v2 >= 0); geom->edges_.append({static_cast<uint>(edge_v1), static_cast<uint>(edge_v2)}); + geom->track_vertex_index(edge_v1); + geom->track_vertex_index(edge_v2); } static void geom_add_polygon(Geometry *geom, const char *p, const char *end, const GlobalVertices &global_vertices, - const VertexIndexOffset &offsets, const int material_index, const int group_index, const bool shaded_smooth) @@ -170,7 +182,7 @@ static void geom_add_polygon(Geometry *geom, curr_face.material_index = material_index; if (group_index >= 0) { curr_face.vertex_group_index = group_index; - geom->use_vertex_groups_ = true; + geom->has_vertex_groups_ = true; } const int orig_corners_size = geom->face_corners_.size(); @@ -199,8 +211,7 @@ static void geom_add_polygon(Geometry *geom, } } /* 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.vert_index += corner.vert_index < 0 ? global_vertices.vertices.size() : -1; if (corner.vert_index < 0 || corner.vert_index >= global_vertices.vertices.size()) { fprintf(stderr, "Invalid vertex index %i (valid range [0, %zu)), ignoring face\n", @@ -208,6 +219,9 @@ static void geom_add_polygon(Geometry *geom, (size_t)global_vertices.vertices.size()); face_valid = false; } + else { + geom->track_vertex_index(corner.vert_index); + } if (got_uv) { corner.uv_vert_index += corner.uv_vert_index < 0 ? global_vertices.uv_vertices.size() : -1; if (corner.uv_vert_index < 0 || corner.uv_vert_index >= global_vertices.uv_vertices.size()) { @@ -221,7 +235,7 @@ static void geom_add_polygon(Geometry *geom, /* Ignore corner normal index, if the geometry does not have any normals. * Some obj files out there do have face definitions that refer to normal indices, * without any normals being present (T98782). */ - if (got_normal && geom->has_vertex_normals_) { + if (got_normal && !global_vertices.vertex_normals.is_empty()) { corner.vertex_normal_index += corner.vertex_normal_index < 0 ? global_vertices.vertex_normals.size() : -1; @@ -255,9 +269,7 @@ static void geom_add_polygon(Geometry *geom, static Geometry *geom_set_curve_type(Geometry *geom, const char *p, const char *end, - const GlobalVertices &global_vertices, const StringRef group_name, - VertexIndexOffset &r_offsets, Vector<std::unique_ptr<Geometry>> &r_all_geometries) { p = drop_whitespace(p, end); @@ -265,8 +277,7 @@ static Geometry *geom_set_curve_type(Geometry *geom, std::cerr << "Curve type not supported: '" << std::string(p, end) << "'" << std::endl; return geom; } - geom = create_geometry( - geom, GEOM_CURVE, group_name, global_vertices, r_all_geometries, r_offsets); + geom = create_geometry(geom, GEOM_CURVE, group_name, r_all_geometries); geom->nurbs_element_.group_ = group_name; return geom; } @@ -385,6 +396,22 @@ static bool parse_keyword(const char *&p, const char *end, StringRef keyword) return true; } +/* Special case: if there were no faces/edges in any geometries, + * treat all the vertices as a point cloud. */ +static void use_all_vertices_if_no_faces(Geometry *geom, + const Vector<std::unique_ptr<Geometry>> &all_geometries, + const GlobalVertices &global_vertices) +{ + if (!global_vertices.vertices.is_empty() && geom && geom->geom_type_ == GEOM_MESH) { + if (std::all_of( + all_geometries.begin(), all_geometries.end(), [](const std::unique_ptr<Geometry> &g) { + return g->get_vertex_count() == 0; + })) { + geom->track_all_vertices(global_vertices.vertices.size()); + } + } +} + void OBJParser::parse(Vector<std::unique_ptr<Geometry>> &r_all_geometries, GlobalVertices &r_global_vertices) { @@ -397,9 +424,7 @@ void OBJParser::parse(Vector<std::unique_ptr<Geometry>> &r_all_geometries, BLI_strncpy(ob_name, BLI_path_basename(import_params_.filepath), FILE_MAXFILE); BLI_path_extension_replace(ob_name, FILE_MAXFILE, ""); - VertexIndexOffset offsets; - Geometry *curr_geom = create_geometry( - nullptr, GEOM_MESH, ob_name, r_global_vertices, r_all_geometries, offsets); + Geometry *curr_geom = create_geometry(nullptr, GEOM_MESH, ob_name, r_all_geometries); /* State variables: once set, they remain the same for the remaining * elements in the object. */ @@ -422,6 +447,11 @@ void OBJParser::parse(Vector<std::unique_ptr<Geometry>> &r_all_geometries, break; /* No more data to read. */ } + /* Take care of line continuations now (turn them into spaces); + * the rest of the parsing code does not need to worry about them anymore. */ + fixup_line_continuations(buffer.data() + buffer_offset, + buffer.data() + buffer_offset + bytes_read); + /* Ensure buffer ends in a newline. */ if (bytes_read < read_buffer_size_) { if (bytes_read == 0 || buffer[buffer_offset + bytes_read - 1] != '\n') { @@ -440,9 +470,7 @@ void OBJParser::parse(Vector<std::unique_ptr<Geometry>> &r_all_geometries, while (last_nl > 0) { --last_nl; if (buffer[last_nl] == '\n') { - if (last_nl < 1 || buffer[last_nl - 1] != '\\') { - break; - } + break; } } if (buffer[last_nl] != '\n') { @@ -469,10 +497,10 @@ void OBJParser::parse(Vector<std::unique_ptr<Geometry>> &r_all_geometries, /* Most common things that start with 'v': vertices, normals, UVs. */ if (*p == 'v') { if (parse_keyword(p, end, "v")) { - geom_add_vertex(curr_geom, p, end, r_global_vertices); + geom_add_vertex(p, end, r_global_vertices); } else if (parse_keyword(p, end, "vn")) { - geom_add_vertex_normal(curr_geom, p, end, r_global_vertices); + geom_add_vertex_normal(p, end, r_global_vertices); } else if (parse_keyword(p, end, "vt")) { geom_add_uv_vertex(p, end, r_global_vertices); @@ -484,26 +512,21 @@ void OBJParser::parse(Vector<std::unique_ptr<Geometry>> &r_all_geometries, p, end, r_global_vertices, - offsets, state_material_index, - state_group_index, /* TODO was wrongly material name! */ + state_group_index, state_shaded_smooth); } /* Faces. */ else if (parse_keyword(p, end, "l")) { - geom_add_edge(curr_geom, p, end, offsets, r_global_vertices); + geom_add_edge(curr_geom, p, end, r_global_vertices); } /* Objects. */ else if (parse_keyword(p, end, "o")) { state_shaded_smooth = false; state_group_name = ""; state_material_name = ""; - curr_geom = create_geometry(curr_geom, - GEOM_MESH, - StringRef(p, end).trim(), - r_global_vertices, - r_all_geometries, - offsets); + curr_geom = create_geometry( + curr_geom, GEOM_MESH, StringRef(p, end).trim(), r_all_geometries); } /* Groups. */ else if (parse_keyword(p, end, "g")) { @@ -532,7 +555,7 @@ void OBJParser::parse(Vector<std::unique_ptr<Geometry>> &r_all_geometries, add_mtl_library(StringRef(p, end).trim()); } else if (parse_keyword(p, end, "#MRGB")) { - geom_add_mrgb_colors(curr_geom, p, end, r_global_vertices); + geom_add_mrgb_colors(p, end, r_global_vertices); } /* Comments. */ else if (*p == '#') { @@ -540,8 +563,7 @@ void OBJParser::parse(Vector<std::unique_ptr<Geometry>> &r_all_geometries, } /* Curve related things. */ else if (parse_keyword(p, end, "cstype")) { - curr_geom = geom_set_curve_type( - curr_geom, p, end, r_global_vertices, state_group_name, offsets, r_all_geometries); + curr_geom = geom_set_curve_type(curr_geom, p, end, state_group_name, r_all_geometries); } else if (parse_keyword(p, end, "deg")) { geom_set_curve_degree(curr_geom, p, end); @@ -567,39 +589,35 @@ void OBJParser::parse(Vector<std::unique_ptr<Geometry>> &r_all_geometries, buffer_offset = left_size; } + use_all_vertices_if_no_faces(curr_geom, r_all_geometries, r_global_vertices); add_default_mtl_library(); } -static eMTLSyntaxElement mtl_line_start_to_enum(const char *&p, const char *end) +static MTLTexMapType mtl_line_start_to_texture_type(const char *&p, const char *end) { if (parse_keyword(p, end, "map_Kd")) { - return eMTLSyntaxElement::map_Kd; + return MTLTexMapType::Kd; } if (parse_keyword(p, end, "map_Ks")) { - return eMTLSyntaxElement::map_Ks; + return MTLTexMapType::Ks; } if (parse_keyword(p, end, "map_Ns")) { - return eMTLSyntaxElement::map_Ns; + return MTLTexMapType::Ns; } if (parse_keyword(p, end, "map_d")) { - return eMTLSyntaxElement::map_d; + return MTLTexMapType::d; } - if (parse_keyword(p, end, "refl")) { - return eMTLSyntaxElement::map_refl; - } - if (parse_keyword(p, end, "map_refl")) { - return eMTLSyntaxElement::map_refl; + if (parse_keyword(p, end, "refl") || parse_keyword(p, end, "map_refl")) { + return MTLTexMapType::refl; } if (parse_keyword(p, end, "map_Ke")) { - return eMTLSyntaxElement::map_Ke; - } - if (parse_keyword(p, end, "bump")) { - return eMTLSyntaxElement::map_Bump; + return MTLTexMapType::Ke; } - if (parse_keyword(p, end, "map_Bump") || parse_keyword(p, end, "map_bump")) { - return eMTLSyntaxElement::map_Bump; + if (parse_keyword(p, end, "bump") || parse_keyword(p, end, "map_Bump") || + parse_keyword(p, end, "map_bump")) { + return MTLTexMapType::bump; } - return eMTLSyntaxElement::string; + return MTLTexMapType::Count; } static const std::pair<StringRef, int> unsupported_texture_options[] = { @@ -617,19 +635,19 @@ static const std::pair<StringRef, int> unsupported_texture_options[] = { static bool parse_texture_option(const char *&p, const char *end, MTLMaterial *material, - tex_map_XX &tex_map) + MTLTexMap &tex_map) { p = drop_whitespace(p, end); if (parse_keyword(p, end, "-o")) { - p = parse_floats(p, end, 0.0f, tex_map.translation, 3); + p = parse_floats(p, end, 0.0f, tex_map.translation, 3, true); return true; } if (parse_keyword(p, end, "-s")) { - p = parse_floats(p, end, 1.0f, tex_map.scale, 3); + p = parse_floats(p, end, 1.0f, tex_map.scale, 3, true); return true; } if (parse_keyword(p, end, "-bm")) { - p = parse_float(p, end, 1.0f, material->map_Bump_strength); + p = parse_float(p, end, 1.0f, material->map_Bump_strength, true, true); return true; } if (parse_keyword(p, end, "-type")) { @@ -671,13 +689,13 @@ static void parse_texture_map(const char *p, if (!is_map && !is_refl && !is_bump) { return; } - eMTLSyntaxElement key = mtl_line_start_to_enum(p, end); - if (key == eMTLSyntaxElement::string || !material->texture_maps.contains(key)) { + MTLTexMapType key = mtl_line_start_to_texture_type(p, end); + if (key == MTLTexMapType::Count) { /* No supported texture map found. */ std::cerr << "OBJ import: MTL texture map type not supported: '" << line << "'" << std::endl; return; } - tex_map_XX &tex_map = material->texture_maps.lookup(key); + MTLTexMap &tex_map = material->tex_map_of_type(key); tex_map.mtl_dir_path = mtl_dir_path; /* Parse texture map options. */ @@ -726,7 +744,7 @@ MTLParser::MTLParser(StringRefNull 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_path_join(mtl_file_path_, FILE_MAX, obj_file_dir, mtl_library.data(), nullptr); BLI_split_dir_part(mtl_file_path_, mtl_dir_path_, FILE_MAXDIR); } 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 acc35ad46e1..b1a2c7834f4 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc +++ b/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc @@ -8,8 +8,9 @@ #include "DNA_mesh_types.h" #include "DNA_scene_types.h" -#include "BKE_attribute.h" +#include "BKE_attribute.hh" #include "BKE_customdata.h" +#include "BKE_deform.h" #include "BKE_material.h" #include "BKE_mesh.h" #include "BKE_node_tree_update.h" @@ -21,6 +22,7 @@ #include "IO_wavefront_obj.h" #include "importer_mesh_utils.hh" +#include "obj_export_mtl.hh" #include "obj_import_mesh.hh" namespace blender::io::obj { @@ -30,13 +32,17 @@ Object *MeshFromGeometry::create_mesh(Main *bmain, Map<std::string, Material *> &created_materials, const OBJImportParams &import_params) { + const int64_t tot_verts_object{mesh_geometry_.get_vertex_count()}; + if (tot_verts_object <= 0) { + /* Empty mesh */ + return nullptr; + } std::string ob_name{mesh_geometry_.geometry_name_}; if (ob_name.empty()) { ob_name = "Untitled"; } fixup_invalid_faces(); - const int64_t tot_verts_object{mesh_geometry_.vertex_count_}; /* Total explicitly imported edges, not the ones belonging the polygons to be created. */ const int64_t tot_edges{mesh_geometry_.edges_.size()}; const int64_t tot_face_elems{mesh_geometry_.face_elements_.size()}; @@ -47,12 +53,12 @@ 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(obj, mesh); + create_polys_loops(mesh, import_params.import_vertex_groups); create_edges(mesh); create_uv_verts(mesh); create_normals(mesh); create_colors(mesh); - create_materials(bmain, materials, created_materials, obj); + create_materials(bmain, materials, created_materials, obj, import_params.relative_paths); if (import_params.validate_meshes || mesh_geometry_.has_invalid_polys_) { bool verbose_validate = false; @@ -69,6 +75,9 @@ Object *MeshFromGeometry::create_mesh(Main *bmain, BKE_mesh_nomain_to_mesh(mesh, dst, obj, &CD_MASK_EVERYTHING, true); dst->flag |= autosmooth; + /* NOTE: vertex groups have to be created after final mesh is assigned to the object. */ + create_vertex_groups(obj); + return obj; } @@ -149,35 +158,39 @@ void MeshFromGeometry::fixup_invalid_faces() void MeshFromGeometry::create_vertices(Mesh *mesh) { - const int tot_verts_object{mesh_geometry_.vertex_count_}; - for (int i = 0; i < tot_verts_object; ++i) { - int vi = mesh_geometry_.vertex_start_ + i; - if (vi < global_vertices_.vertices.size()) { - copy_v3_v3(mesh->mvert[i].co, global_vertices_.vertices[vi]); - } - else { - std::cerr << "Vertex index:" << vi - << " larger than total vertices:" << global_vertices_.vertices.size() << " ." - << std::endl; + MutableSpan<MVert> verts = mesh->verts_for_write(); + /* Go through all the global vertex indices from min to max, + * checking which ones are actually and building a global->local + * index mapping. Write out the used vertex positions into the Mesh + * data. */ + mesh_geometry_.global_to_local_vertices_.clear(); + mesh_geometry_.global_to_local_vertices_.reserve(mesh_geometry_.vertices_.size()); + for (int vi = mesh_geometry_.vertex_index_min_; vi <= mesh_geometry_.vertex_index_max_; ++vi) { + BLI_assert(vi >= 0 && vi < global_vertices_.vertices.size()); + if (!mesh_geometry_.vertices_.contains(vi)) { + continue; } + int local_vi = (int)mesh_geometry_.global_to_local_vertices_.size(); + BLI_assert(local_vi >= 0 && local_vi < mesh->totvert); + copy_v3_v3(verts[local_vi].co, global_vertices_.vertices[vi]); + mesh_geometry_.global_to_local_vertices_.add_new(vi, local_vi); } } -void MeshFromGeometry::create_polys_loops(Object *obj, Mesh *mesh) +void MeshFromGeometry::create_polys_loops(Mesh *mesh, bool use_vertex_groups) { - /* Will not be used if vertex groups are not imported. */ - mesh->dvert = nullptr; - float weight = 0.0f; - const int64_t total_verts = mesh_geometry_.vertex_count_; - if (total_verts && mesh_geometry_.use_vertex_groups_) { - mesh->dvert = static_cast<MDeformVert *>( - CustomData_add_layer(&mesh->vdata, CD_MDEFORMVERT, CD_CALLOC, nullptr, total_verts)); - weight = 1.0f / total_verts; - } - else { - UNUSED_VARS(weight); + MutableSpan<MDeformVert> dverts; + const int64_t total_verts = mesh_geometry_.get_vertex_count(); + if (use_vertex_groups && total_verts && mesh_geometry_.has_vertex_groups_) { + dverts = mesh->deform_verts_for_write(); } + MutableSpan<MPoly> polys = mesh->polys_for_write(); + MutableSpan<MLoop> loops = mesh->loops_for_write(); + bke::SpanAttributeWriter<int> material_indices = + mesh->attributes_for_write().lookup_or_add_for_write_only_span<int>("material_index", + ATTR_DOMAIN_FACE); + const int64_t tot_face_elems{mesh->totpoly}; int tot_loop_idx = 0; @@ -189,47 +202,44 @@ void MeshFromGeometry::create_polys_loops(Object *obj, Mesh *mesh) continue; } - MPoly &mpoly = mesh->mpoly[poly_idx]; + MPoly &mpoly = polys[poly_idx]; mpoly.totloop = curr_face.corner_count_; mpoly.loopstart = tot_loop_idx; if (curr_face.shaded_smooth) { mpoly.flag |= ME_SMOOTH; } - mpoly.mat_nr = curr_face.material_index; + material_indices.span[poly_idx] = curr_face.material_index; /* Importing obj files without any materials would result in negative indices, which is not * supported. */ - if (mpoly.mat_nr < 0) { - mpoly.mat_nr = 0; + if (material_indices.span[poly_idx] < 0) { + material_indices.span[poly_idx] = 0; } for (int idx = 0; idx < curr_face.corner_count_; ++idx) { const PolyCorner &curr_corner = mesh_geometry_.face_corners_[curr_face.start_index_ + idx]; - MLoop &mloop = mesh->mloop[tot_loop_idx]; + MLoop &mloop = loops[tot_loop_idx]; tot_loop_idx++; - mloop.v = curr_corner.vert_index; + mloop.v = mesh_geometry_.global_to_local_vertices_.lookup_default(curr_corner.vert_index, 0); - if (!mesh->dvert) { + /* Setup vertex group data, if needed. */ + if (dverts.is_empty()) { continue; } - /* 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 = 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. */ - int group_idx = curr_face.vertex_group_index; - /* Deform group number (def_nr) must behave like an index into the names' list. */ - *(def_vert.dw) = {static_cast<unsigned int>(group_idx), weight}; + const int group_index = curr_face.vertex_group_index; + MDeformWeight *dw = BKE_defvert_ensure_index(&dverts[mloop.v], group_index); + dw->weight = 1.0f; } } - if (!mesh->dvert) { + material_indices.finish(); +} + +void MeshFromGeometry::create_vertex_groups(Object *obj) +{ + Mesh *mesh = static_cast<Mesh *>(obj->data); + if (mesh->deform_verts().is_empty()) { return; } - /* Add deform group names. */ for (const std::string &name : mesh_geometry_.group_order_) { BKE_object_defgroup_add_name(obj, name.data()); } @@ -237,15 +247,17 @@ void MeshFromGeometry::create_polys_loops(Object *obj, Mesh *mesh) void MeshFromGeometry::create_edges(Mesh *mesh) { + MutableSpan<MEdge> edges = mesh->edges_for_write(); + const int64_t tot_edges{mesh_geometry_.edges_.size()}; - const int64_t total_verts{mesh_geometry_.vertex_count_}; + const int64_t total_verts{mesh_geometry_.get_vertex_count()}; UNUSED_VARS_NDEBUG(total_verts); for (int i = 0; i < tot_edges; ++i) { const MEdge &src_edge = mesh_geometry_.edges_[i]; - MEdge &dst_edge = mesh->medge[i]; - BLI_assert(src_edge.v1 < total_verts && src_edge.v2 < total_verts); - dst_edge.v1 = src_edge.v1; - dst_edge.v2 = src_edge.v2; + MEdge &dst_edge = edges[i]; + dst_edge.v1 = mesh_geometry_.global_to_local_vertices_.lookup_default(src_edge.v1, 0); + dst_edge.v2 = mesh_geometry_.global_to_local_vertices_.lookup_default(src_edge.v2, 0); + BLI_assert(dst_edge.v1 < total_verts && dst_edge.v2 < total_verts); dst_edge.flag = ME_LOOSEEDGE; } @@ -261,7 +273,7 @@ void MeshFromGeometry::create_uv_verts(Mesh *mesh) return; } MLoopUV *mluv_dst = static_cast<MLoopUV *>(CustomData_add_layer( - &mesh->ldata, CD_MLOOPUV, CD_DEFAULT, nullptr, mesh_geometry_.total_loops_)); + &mesh->ldata, CD_MLOOPUV, CD_SET_DEFAULT, nullptr, mesh_geometry_.total_loops_)); int tot_loop_idx = 0; for (const PolyElem &curr_face : mesh_geometry_.face_elements_) { @@ -280,7 +292,8 @@ void MeshFromGeometry::create_uv_verts(Mesh *mesh) static Material *get_or_create_material(Main *bmain, const std::string &name, Map<std::string, std::unique_ptr<MTLMaterial>> &materials, - Map<std::string, Material *> &created_materials) + Map<std::string, Material *> &created_materials, + bool relative_paths) { /* Have we created this material already? */ Material **found_mat = created_materials.lookup_ptr(name); @@ -294,9 +307,10 @@ static Material *get_or_create_material(Main *bmain, const MTLMaterial &mtl = *materials.lookup_or_add(name, std::make_unique<MTLMaterial>()); Material *mat = BKE_material_add(bmain, name.c_str()); - ShaderNodetreeWrap mat_wrap{bmain, mtl, mat}; + id_us_min(&mat->id); + mat->use_nodes = true; - mat->nodetree = mat_wrap.get_nodetree(); + mat->nodetree = create_mtl_node_tree(bmain, mtl, mat, relative_paths); BKE_ntree_update_main_tree(bmain, mat->nodetree, nullptr); created_materials.add_new(name, mat); @@ -306,24 +320,30 @@ static Material *get_or_create_material(Main *bmain, void MeshFromGeometry::create_materials(Main *bmain, Map<std::string, std::unique_ptr<MTLMaterial>> &materials, Map<std::string, Material *> &created_materials, - Object *obj) + Object *obj, + bool relative_paths) { for (const std::string &name : mesh_geometry_.material_order_) { - Material *mat = get_or_create_material(bmain, name, materials, created_materials); + Material *mat = get_or_create_material( + bmain, name, materials, created_materials, relative_paths); if (mat == nullptr) { continue; } - BKE_object_material_slot_add(bmain, obj); - BKE_object_material_assign(bmain, obj, mat, obj->totcol, BKE_MAT_ASSIGN_USERPREF); + BKE_object_material_assign_single_obdata(bmain, obj, mat, obj->totcol + 1); + } + if (obj->totcol > 0) { + obj->actcol = 1; } } void MeshFromGeometry::create_normals(Mesh *mesh) { - /* NOTE: Needs more clarity about what is expected in the viewport if the function works. */ - /* No normal data: nothing to do. */ - if (global_vertices_.vertex_normals.is_empty() || !mesh_geometry_.has_vertex_normals_) { + if (global_vertices_.vertex_normals.is_empty()) { + return; + } + /* Custom normals can only be stored on face corners. */ + if (mesh_geometry_.total_loops_ == 0) { return; } @@ -349,23 +369,26 @@ void MeshFromGeometry::create_normals(Mesh *mesh) void MeshFromGeometry::create_colors(Mesh *mesh) { - /* Nothing to do if we don't have vertex colors. */ - if (mesh_geometry_.vertex_color_count_ < 1) { - return; - } - if (mesh_geometry_.vertex_color_count_ != mesh_geometry_.vertex_count_) { - std::cerr << "Mismatching number of vertices (" << mesh_geometry_.vertex_count_ - << ") and colors (" << mesh_geometry_.vertex_color_count_ << ") on object '" - << mesh_geometry_.geometry_name_ << "', ignoring colors." << std::endl; + /* Nothing to do if we don't have vertex colors at all. */ + if (global_vertices_.vertex_colors.is_empty()) { return; } - CustomDataLayer *color_layer = BKE_id_attribute_new( - &mesh->id, "Color", CD_PROP_COLOR, ATTR_DOMAIN_POINT, nullptr); - float4 *colors = (float4 *)color_layer->data; - for (int i = 0; i < mesh_geometry_.vertex_color_count_; ++i) { - float3 c = global_vertices_.vertex_colors[mesh_geometry_.vertex_color_start_ + i]; - colors[i] = float4(c.x, c.y, c.z, 1.0f); + /* Find which vertex color block is for this mesh (if any). */ + for (const auto &block : global_vertices_.vertex_colors) { + if (mesh_geometry_.vertex_index_min_ >= block.start_vertex_index && + mesh_geometry_.vertex_index_max_ < block.start_vertex_index + block.colors.size()) { + /* This block is suitable, use colors from it. */ + CustomDataLayer *color_layer = BKE_id_attribute_new( + &mesh->id, "Color", CD_PROP_COLOR, ATTR_DOMAIN_POINT, nullptr); + float4 *colors = (float4 *)color_layer->data; + int offset = mesh_geometry_.vertex_index_min_ - block.start_vertex_index; + for (int i = 0, n = mesh_geometry_.get_vertex_count(); i != n; ++i) { + float3 c = block.colors[offset + i]; + colors[i] = float4(c.x, c.y, c.z, 1.0f); + } + return; + } } } diff --git a/source/blender/io/wavefront_obj/importer/obj_import_mesh.hh b/source/blender/io/wavefront_obj/importer/obj_import_mesh.hh index 216717f3578..cdc88528f1e 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_mesh.hh +++ b/source/blender/io/wavefront_obj/importer/obj_import_mesh.hh @@ -45,10 +45,9 @@ class MeshFromGeometry : NonMovable, NonCopyable { void fixup_invalid_faces(); void create_vertices(Mesh *mesh); /** - * Create polygons for the Mesh, set smooth shading flags, deform group names, - * Materials. + * Create polygons for the Mesh, set smooth shading flags, Materials. */ - void create_polys_loops(Object *obj, Mesh *mesh); + void create_polys_loops(Mesh *mesh, bool use_vertex_groups); /** * Add explicitly imported OBJ edges to the mesh. */ @@ -63,9 +62,11 @@ class MeshFromGeometry : NonMovable, NonCopyable { void create_materials(Main *bmain, Map<std::string, std::unique_ptr<MTLMaterial>> &materials, Map<std::string, Material *> &created_materials, - Object *obj); + Object *obj, + bool relative_paths); void create_normals(Mesh *mesh); void create_colors(Mesh *mesh); + void create_vertex_groups(Object *obj); }; } // namespace blender::io::obj diff --git a/source/blender/io/wavefront_obj/importer/obj_import_mtl.cc b/source/blender/io/wavefront_obj/importer/obj_import_mtl.cc index f39def0a4af..0922a71979e 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_mtl.cc +++ b/source/blender/io/wavefront_obj/importer/obj_import_mtl.cc @@ -5,18 +5,19 @@ */ #include "BKE_image.h" +#include "BKE_main.h" #include "BKE_node.h" #include "BLI_map.hh" #include "BLI_math_vector.h" +#include "BLI_path_util.h" #include "DNA_material_types.h" #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_export_mtl.hh" #include "obj_import_mtl.hh" #include "obj_import_string_utils.hh" @@ -27,12 +28,12 @@ namespace blender::io::obj { * Only float value(s) can be set using this method. */ static void set_property_of_socket(eNodeSocketDatatype property_type, - StringRef socket_id, + const char *socket_id, Span<float> value, bNode *r_node) { BLI_assert(r_node); - bNodeSocket *socket{nodeFindSocket(r_node, SOCK_IN, socket_id.data())}; + bNodeSocket *socket{nodeFindSocket(r_node, SOCK_IN, socket_id)}; BLI_assert(socket && socket->type == property_type); switch (property_type) { case SOCK_FLOAT: { @@ -60,136 +61,124 @@ static void set_property_of_socket(eNodeSocketDatatype property_type, } } -static bool load_texture_image_at_path(Main *bmain, - const tex_map_XX &tex_map, - bNode *r_node, - const std::string &path) +static Image *load_image_at_path(Main *bmain, const std::string &path, bool relative_paths) { - Image *tex_image = BKE_image_load(bmain, path.c_str()); - if (!tex_image) { + Image *image = BKE_image_load_exists(bmain, path.c_str()); + if (!image) { fprintf(stderr, "Cannot load image file: '%s'\n", path.c_str()); - return false; + return nullptr; } fprintf(stderr, "Loaded image from: '%s'\n", path.c_str()); - r_node->id = reinterpret_cast<ID *>(tex_image); - NodeTexImage *image = static_cast<NodeTexImage *>(r_node->storage); - image->projection = tex_map.projection_type; - return true; + if (relative_paths) { + BLI_path_rel(image->filepath, BKE_main_blendfile_path(bmain)); + } + return image; } -/** - * 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) +static Image *create_placeholder_image(Main *bmain, const std::string &path) +{ + const float color[4] = {0, 0, 0, 1}; + Image *image = BKE_image_add_generated(bmain, + 32, + 32, + BLI_path_basename(path.c_str()), + 24, + false, + IMA_GENTYPE_BLANK, + color, + false, + false, + false); + STRNCPY(image->filepath, path.c_str()); + image->source = IMA_SRC_FILE; + return image; +} + +static Image *load_texture_image(Main *bmain, const MTLTexMap &tex_map, bool relative_paths) { - BLI_assert(r_node && r_node->type == SH_NODE_TEX_IMAGE); + Image *image = nullptr; /* First try treating texture path as relative. */ std::string tex_path{tex_map.mtl_dir_path + tex_map.image_path}; - if (load_texture_image_at_path(bmain, tex_map, r_node, tex_path)) { - return true; + image = load_image_at_path(bmain, tex_path, relative_paths); + if (image != nullptr) { + return image; } /* Then try using it directly as absolute path. */ std::string raw_path{tex_map.image_path}; - if (load_texture_image_at_path(bmain, tex_map, r_node, raw_path)) { - return true; + image = load_image_at_path(bmain, raw_path, relative_paths); + if (image != nullptr) { + return image; } /* Try removing quotes. */ std::string no_quote_path{tex_path}; auto end_pos = std::remove(no_quote_path.begin(), no_quote_path.end(), '"'); no_quote_path.erase(end_pos, no_quote_path.end()); - if (no_quote_path != tex_path && - load_texture_image_at_path(bmain, tex_map, r_node, no_quote_path)) { - return true; + if (no_quote_path != tex_path) { + image = load_image_at_path(bmain, no_quote_path, relative_paths); + if (image != nullptr) { + return image; + } } /* Try replacing underscores with spaces. */ std::string no_underscore_path{no_quote_path}; std::replace(no_underscore_path.begin(), no_underscore_path.end(), '_', ' '); - if (no_underscore_path != no_quote_path && no_underscore_path != tex_path && - load_texture_image_at_path(bmain, tex_map, r_node, no_underscore_path)) { - return true; + if (no_underscore_path != no_quote_path && no_underscore_path != tex_path) { + image = load_image_at_path(bmain, no_underscore_path, relative_paths); + if (image != nullptr) { + return image; + } } - - return false; -} - -ShaderNodetreeWrap::ShaderNodetreeWrap(Main *bmain, const MTLMaterial &mtl_mat, Material *mat) - : mtl_mat_(mtl_mat) -{ - nodetree_.reset(ntreeAddTree(nullptr, "Shader Nodetree", ntreeType_Shader->idname)); - bsdf_ = add_node_to_tree(SH_NODE_BSDF_PRINCIPLED); - shader_output_ = add_node_to_tree(SH_NODE_OUTPUT_MATERIAL); - - set_bsdf_socket_values(mat); - add_image_textures(bmain, mat); - link_sockets(bsdf_, "BSDF", shader_output_, "Surface", 4); - - nodeSetActive(nodetree_.get(), shader_output_); -} - -/** - * 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); + /* Try taking just the basename from input path. */ + std::string base_path{tex_map.mtl_dir_path + BLI_path_basename(tex_map.image_path.c_str())}; + if (base_path != tex_path) { + image = load_image_at_path(bmain, base_path, relative_paths); + if (image != nullptr) { + return image; + } } -} -bNodeTree *ShaderNodetreeWrap::get_nodetree() -{ - /* If this function has been reached, we know that nodes and the nodetree - * can be added to the scene safely. */ - return nodetree_.release(); + image = create_placeholder_image(bmain, tex_path); + return image; } -bNode *ShaderNodetreeWrap::add_node_to_tree(const int node_type) -{ - return nodeAddStaticNode(nullptr, nodetree_.get(), node_type); -} +/* Nodes are arranged in columns by type, with manually placed x coordinates + * based on node widths. */ +const float node_locx_texcoord = -880.0f; +const float node_locx_mapping = -680.0f; +const float node_locx_image = -480.0f; +const float node_locx_normalmap = -200.0f; +const float node_locx_bsdf = 0.0f; +const float node_locx_output = 280.0f; -std::pair<float, float> ShaderNodetreeWrap::set_node_locations(const int pos_x) +/* Nodes are arranged in rows; one row for each image being used. */ +const float node_locy_top = 300.0f; +const float node_locy_step = 300.0f; + +/* Add a node of the given type at the given location. */ +static bNode *add_node(bNodeTree *ntree, int type, float x, float y) { - 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}; - } - } + bNode *node = nodeAddStaticNode(nullptr, ntree, type); + node->locx = x; + node->locy = y; + return node; } -void ShaderNodetreeWrap::link_sockets(bNode *from_node, - StringRef from_node_id, - bNode *to_node, - StringRef to_node_id, - const int from_node_pos_x) +static void link_sockets(bNodeTree *ntree, + bNode *from_node, + const char *from_node_id, + bNode *to_node, + const char *to_node_id) { - 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, SOCK_OUT, from_node_id.data())}; - bNodeSocket *to_sock{nodeFindSocket(to_node, SOCK_IN, to_node_id.data())}; + bNodeSocket *from_sock{nodeFindSocket(from_node, SOCK_OUT, from_node_id)}; + bNodeSocket *to_sock{nodeFindSocket(to_node, SOCK_IN, to_node_id)}; BLI_assert(from_sock && to_sock); - nodeAddLink(nodetree_.get(), from_node, from_sock, to_node, to_sock); + nodeAddLink(ntree, from_node, from_sock, to_node, to_sock); } -void ShaderNodetreeWrap::set_bsdf_socket_values(Material *mat) +static void set_bsdf_socket_values(bNode *bsdf, Material *mat, const MTLMaterial &mtl_mat) { - const int illum = mtl_mat_.illum; + const int illum = mtl_mat.illum; bool do_highlight = false; bool do_tranparency = false; bool do_reflection = false; @@ -255,21 +244,21 @@ void ShaderNodetreeWrap::set_bsdf_socket_values(Material *mat) /* Approximations for trying to map obj/mtl material model into * Principled BSDF: */ /* Specular: average of Ks components. */ - float specular = (mtl_mat_.Ks[0] + mtl_mat_.Ks[1] + mtl_mat_.Ks[2]) / 3; + float specular = (mtl_mat.Ks[0] + mtl_mat.Ks[1] + mtl_mat.Ks[2]) / 3; if (specular < 0.0f) { specular = do_highlight ? 1.0f : 0.0f; } /* Roughness: map 0..1000 range to 1..0 and apply non-linearity. */ float roughness; - if (mtl_mat_.Ns < 0.0f) { + if (mtl_mat.Ns < 0.0f) { roughness = do_highlight ? 0.0f : 1.0f; } else { - float clamped_ns = std::max(0.0f, std::min(1000.0f, mtl_mat_.Ns)); + float clamped_ns = std::max(0.0f, std::min(1000.0f, mtl_mat.Ns)); roughness = 1.0f - sqrt(clamped_ns / 1000.0f); } /* Metallic: average of Ka components. */ - float metallic = (mtl_mat_.Ka[0] + mtl_mat_.Ka[1] + mtl_mat_.Ka[2]) / 3; + float metallic = (mtl_mat.Ka[0] + mtl_mat.Ka[1] + mtl_mat.Ka[2]) / 3; if (do_reflection) { if (metallic < 0.0f) { metallic = 1.0f; @@ -279,7 +268,7 @@ void ShaderNodetreeWrap::set_bsdf_socket_values(Material *mat) metallic = 0.0f; } - float ior = mtl_mat_.Ni; + float ior = mtl_mat.Ni; if (ior < 0) { if (do_tranparency) { ior = 1.0f; @@ -288,89 +277,121 @@ void ShaderNodetreeWrap::set_bsdf_socket_values(Material *mat) ior = 1.5f; } } - float alpha = mtl_mat_.d; + float alpha = mtl_mat.d; if (do_tranparency && alpha < 0) { alpha = 1.0f; } - float3 base_color = {mtl_mat_.Kd[0], mtl_mat_.Kd[1], mtl_mat_.Kd[2]}; + float3 base_color = {mtl_mat.Kd[0], mtl_mat.Kd[1], mtl_mat.Kd[2]}; if (base_color.x >= 0 && base_color.y >= 0 && base_color.z >= 0) { - set_property_of_socket(SOCK_RGBA, "Base Color", {base_color, 3}, bsdf_); + set_property_of_socket(SOCK_RGBA, "Base Color", {base_color, 3}, bsdf); /* Viewport shading uses legacy r,g,b base color. */ mat->r = base_color.x; mat->g = base_color.y; mat->b = base_color.z; } - float3 emission_color = {mtl_mat_.Ke[0], mtl_mat_.Ke[1], mtl_mat_.Ke[2]}; + float3 emission_color = {mtl_mat.Ke[0], mtl_mat.Ke[1], mtl_mat.Ke[2]}; if (emission_color.x >= 0 && emission_color.y >= 0 && emission_color.z >= 0) { - set_property_of_socket(SOCK_RGBA, "Emission", {emission_color, 3}, bsdf_); + set_property_of_socket(SOCK_RGBA, "Emission", {emission_color, 3}, bsdf); } - if (mtl_mat_.texture_maps.contains_as(eMTLSyntaxElement::map_Ke)) { - set_property_of_socket(SOCK_FLOAT, "Emission Strength", {1.0f}, bsdf_); + if (mtl_mat.tex_map_of_type(MTLTexMapType::Ke).is_valid()) { + set_property_of_socket(SOCK_FLOAT, "Emission Strength", {1.0f}, bsdf); } - set_property_of_socket(SOCK_FLOAT, "Specular", {specular}, bsdf_); - set_property_of_socket(SOCK_FLOAT, "Roughness", {roughness}, bsdf_); + set_property_of_socket(SOCK_FLOAT, "Specular", {specular}, bsdf); + set_property_of_socket(SOCK_FLOAT, "Roughness", {roughness}, bsdf); mat->roughness = roughness; - set_property_of_socket(SOCK_FLOAT, "Metallic", {metallic}, bsdf_); + set_property_of_socket(SOCK_FLOAT, "Metallic", {metallic}, bsdf); mat->metallic = metallic; if (ior != -1) { - set_property_of_socket(SOCK_FLOAT, "IOR", {ior}, bsdf_); + set_property_of_socket(SOCK_FLOAT, "IOR", {ior}, bsdf); } if (alpha != -1) { - set_property_of_socket(SOCK_FLOAT, "Alpha", {alpha}, bsdf_); + set_property_of_socket(SOCK_FLOAT, "Alpha", {alpha}, bsdf); } - if (do_tranparency) { + if (do_tranparency || (alpha >= 0.0f && alpha < 1.0f)) { mat->blend_method = MA_BM_BLEND; } } -void ShaderNodetreeWrap::add_image_textures(Main *bmain, Material *mat) +static void add_image_textures(Main *bmain, + bNodeTree *ntree, + bNode *bsdf, + Material *mat, + const MTLMaterial &mtl_mat, + bool relative_paths) { - for (const Map<const eMTLSyntaxElement, tex_map_XX>::Item texture_map : - mtl_mat_.texture_maps.items()) { - if (texture_map.value.image_path.empty()) { + float node_locy = node_locy_top; + for (int key = 0; key < (int)MTLTexMapType::Count; ++key) { + const MTLTexMap &value = mtl_mat.texture_maps[key]; + if (!value.is_valid()) { /* No Image texture node of this map type can be added to this material. */ continue; } - bNode *image_texture = add_node_to_tree(SH_NODE_TEX_IMAGE); - if (!load_texture_image(bmain, texture_map.value, image_texture)) { - /* Image could not be added, so don't add or link further nodes. */ + Image *image = load_texture_image(bmain, value, relative_paths); + if (image == nullptr) { continue; } + bNode *image_node = add_node(ntree, SH_NODE_TEX_IMAGE, node_locx_image, node_locy); + BLI_assert(image_node); + image_node->id = &image->id; + static_cast<NodeTexImage *>(image_node->storage)->projection = value.projection_type; + /* Add normal map node if needed. */ bNode *normal_map = nullptr; - if (texture_map.key == eMTLSyntaxElement::map_Bump) { - normal_map = add_node_to_tree(SH_NODE_NORMAL_MAP); - const float bump = std::max(0.0f, mtl_mat_.map_Bump_strength); + if (key == (int)MTLTexMapType::bump) { + normal_map = add_node(ntree, SH_NODE_NORMAL_MAP, node_locx_normalmap, node_locy); + const float bump = std::max(0.0f, mtl_mat.map_Bump_strength); set_property_of_socket(SOCK_FLOAT, "Strength", {bump}, normal_map); } /* Add UV mapping & coordinate nodes only if needed. */ - if (texture_map.value.translation != float3(0, 0, 0) || - texture_map.value.scale != float3(1, 1, 1)) { - bNode *mapping = add_node_to_tree(SH_NODE_MAPPING); - bNode *texture_coordinate = add_node_to_tree(SH_NODE_TEX_COORD); - set_property_of_socket(SOCK_VECTOR, "Location", {texture_map.value.translation, 3}, mapping); - set_property_of_socket(SOCK_VECTOR, "Scale", {texture_map.value.scale, 3}, mapping); - - link_sockets(texture_coordinate, "UV", mapping, "Vector", 0); - link_sockets(mapping, "Vector", image_texture, "Vector", 1); + if (value.translation != float3(0, 0, 0) || value.scale != float3(1, 1, 1)) { + bNode *texcoord = add_node(ntree, SH_NODE_TEX_COORD, node_locx_texcoord, node_locy); + bNode *mapping = add_node(ntree, SH_NODE_MAPPING, node_locx_mapping, node_locy); + set_property_of_socket(SOCK_VECTOR, "Location", {value.translation, 3}, mapping); + set_property_of_socket(SOCK_VECTOR, "Scale", {value.scale, 3}, mapping); + + link_sockets(ntree, texcoord, "UV", mapping, "Vector"); + link_sockets(ntree, mapping, "Vector", image_node, "Vector"); } if (normal_map) { - link_sockets(image_texture, "Color", normal_map, "Color", 2); - link_sockets(normal_map, "Normal", bsdf_, "Normal", 3); + link_sockets(ntree, image_node, "Color", normal_map, "Color"); + link_sockets(ntree, normal_map, "Normal", bsdf, "Normal"); } - else if (texture_map.key == eMTLSyntaxElement::map_d) { - link_sockets(image_texture, "Alpha", bsdf_, texture_map.value.dest_socket_id, 2); + else if (key == (int)MTLTexMapType::d) { + link_sockets(ntree, image_node, "Alpha", bsdf, tex_map_type_to_socket_id[key]); mat->blend_method = MA_BM_BLEND; } else { - link_sockets(image_texture, "Color", bsdf_, texture_map.value.dest_socket_id, 2); + link_sockets(ntree, image_node, "Color", bsdf, tex_map_type_to_socket_id[key]); } + + /* Next layout row: goes downwards on the screen. */ + node_locy -= node_locy_step; } } + +bNodeTree *create_mtl_node_tree(Main *bmain, + const MTLMaterial &mtl, + Material *mat, + bool relative_paths) +{ + bNodeTree *ntree = ntreeAddTreeEmbedded( + nullptr, &mat->id, "Shader Nodetree", ntreeType_Shader->idname); + + bNode *bsdf = add_node(ntree, SH_NODE_BSDF_PRINCIPLED, node_locx_bsdf, node_locy_top); + bNode *output = add_node(ntree, SH_NODE_OUTPUT_MATERIAL, node_locx_output, node_locy_top); + + set_bsdf_socket_values(bsdf, mat, mtl); + add_image_textures(bmain, ntree, bsdf, mat, mtl, relative_paths); + link_sockets(ntree, bsdf, "BSDF", output, "Surface"); + nodeSetActive(ntree, output); + + return ntree; +} + } // namespace blender::io::obj diff --git a/source/blender/io/wavefront_obj/importer/obj_import_mtl.hh b/source/blender/io/wavefront_obj/importer/obj_import_mtl.hh index 12bea1cdc5a..a3ba803e921 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_mtl.hh +++ b/source/blender/io/wavefront_obj/importer/obj_import_mtl.hh @@ -1,93 +1,19 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -/** \file - * \ingroup obj - */ - #pragma once -#include <array> - -#include "BLI_map.hh" -#include "BLI_math_vec_types.hh" -#include "BLI_string_ref.hh" -#include "BLI_vector.hh" - #include "DNA_node_types.h" -#include "MEM_guardedalloc.h" - -#include "obj_export_mtl.hh" +struct Main; +struct Material; namespace blender::io::obj { -struct UniqueNodetreeDeleter { - void operator()(bNodeTree *node) - { - MEM_freeN(node); - } -}; - -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_; - bNode *bsdf_; - bNode *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: - /** - * Initializes a nodetree with a p-BSDF node's BSDF socket connected to shader output node's - * surface socket. - */ - ShaderNodetreeWrap(Main *bmain, const MTLMaterial &mtl_mat, Material *mat); - ~ShaderNodetreeWrap(); - - /** - * Release nodetree for materials to own it. nodetree has its unique deleter - * if destructor is not reached for some reason. - */ - bNodeTree *get_nodetree(); +struct MTLMaterial; - private: - /** - * Add a new static node to the tree. - * No two nodes are linked here. - */ - bNode *add_node_to_tree(const int 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> set_node_locations(const int pos_x); - /** - * 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 link_sockets(bNode *from_node, - StringRef from_node_id, - bNode *to_node, - StringRef to_node_id, - const int from_node_pos_x); - /** - * Set values of sockets in p-BSDF node of the nodetree. - */ - void set_bsdf_socket_values(Material *mat); - /** - * Create image texture, vector and normal mapping nodes from MTL materials and link the - * nodes to p-BSDF node. - */ - void add_image_textures(Main *bmain, Material *mat); -}; +bNodeTree *create_mtl_node_tree(Main *bmain, + const MTLMaterial &mtl_mat, + Material *mat, + bool relative_paths); } // namespace blender::io::obj diff --git a/source/blender/io/wavefront_obj/importer/obj_import_objects.hh b/source/blender/io/wavefront_obj/importer/obj_import_objects.hh index 69babc26bb0..04d9a665588 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_objects.hh +++ b/source/blender/io/wavefront_obj/importer/obj_import_objects.hh @@ -9,9 +9,10 @@ #include "BKE_lib_id.h" #include "BLI_map.hh" +#include "BLI_math_base.hh" #include "BLI_math_vec_types.hh" +#include "BLI_set.hh" #include "BLI_vector.hh" -#include "BLI_vector_set.hh" #include "DNA_meshdata_types.h" #include "DNA_object_types.h" @@ -19,35 +20,24 @@ namespace blender::io::obj { /** - * List of all vertex and UV vertex coordinates in an OBJ file accessible to any - * Geometry instance at any time. + * All vertex positions, normals, UVs, colors in the OBJ file. */ struct GlobalVertices { Vector<float3> vertices; Vector<float2> uv_vertices; Vector<float3> vertex_normals; - Vector<float3> vertex_colors; -}; -/** - * 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_; - } + /** + * Vertex colors might not be present in the file at all, or only + * provided for some meshes. Store them in chunks as they are + * spelled out in the file, e.g. if there are 10 vertices in sequence, all + * with `xyzrgb` colors, they will be one block. + */ + struct VertexColorsBlock { + Vector<float3> colors; + int start_vertex_index; + }; + Vector<VertexColorsBlock> vertex_colors; }; /** @@ -101,21 +91,42 @@ struct Geometry { Map<std::string, int> material_indices_; Vector<std::string> material_order_; - int vertex_start_ = 0; - int vertex_count_ = 0; - int vertex_color_start_ = 0; - int vertex_color_count_ = 0; - /** Edges written in the file in addition to (or even without polygon) elements. */ + int vertex_index_min_ = INT_MAX; + int vertex_index_max_ = -1; + /* Global vertex indices used by this geometry. */ + Set<int> vertices_; + /* Mapping from global vertex index to geometry-local vertex index. */ + Map<int, int> global_to_local_vertices_; + /* Loose edges in the file. */ Vector<MEdge> edges_; Vector<PolyCorner> face_corners_; Vector<PolyElem> face_elements_; bool has_invalid_polys_ = false; - bool has_vertex_normals_ = false; - bool use_vertex_groups_ = false; + bool has_vertex_groups_ = false; NurbsElement nurbs_element_; int total_loops_ = 0; + + int get_vertex_count() const + { + return (int)vertices_.size(); + } + void track_vertex_index(int index) + { + vertices_.add(index); + math::min_inplace(vertex_index_min_, index); + math::max_inplace(vertex_index_max_, index); + } + void track_all_vertices(int count) + { + vertices_.reserve(count); + for (int i = 0; i < count; ++i) { + vertices_.add(i); + } + vertex_index_min_ = 0; + vertex_index_max_ = count - 1; + } }; } // namespace blender::io::obj diff --git a/source/blender/io/wavefront_obj/importer/obj_import_string_utils.cc b/source/blender/io/wavefront_obj/importer/obj_import_string_utils.cc index c8eaa046e68..7e282b164b0 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_string_utils.cc +++ b/source/blender/io/wavefront_obj/importer/obj_import_string_utils.cc @@ -18,14 +18,12 @@ StringRef read_next_line(StringRef &buffer) const char *start = buffer.begin(); const char *end = buffer.end(); size_t len = 0; - char prev = 0; const char *ptr = start; while (ptr < end) { char c = *ptr++; - if (c == '\n' && prev != '\\') { + if (c == '\n') { break; } - prev = c; ++len; } @@ -35,7 +33,29 @@ StringRef read_next_line(StringRef &buffer) static bool is_whitespace(char c) { - return c <= ' ' || c == '\\'; + return c <= ' '; +} + +void fixup_line_continuations(char *p, char *end) +{ + while (true) { + /* Find next backslash, if any. */ + char *backslash = std::find(p, end, '\\'); + if (backslash == end) { + break; + } + /* Skip over possible whitespace right after it. */ + p = backslash + 1; + while (p < end && is_whitespace(*p) && *p != '\n') { + ++p; + } + /* If then we have a newline, turn both backslash + * and the newline into regular spaces. */ + if (p < end && *p == '\n') { + *backslash = ' '; + *p = ' '; + } + } } const char *drop_whitespace(const char *p, const char *end) @@ -62,8 +82,12 @@ static const char *drop_plus(const char *p, const char *end) return p; } -const char *parse_float( - const char *p, const char *end, float fallback, float &dst, bool skip_space) +const char *parse_float(const char *p, + const char *end, + float fallback, + float &dst, + bool skip_space, + bool require_trailing_space) { if (skip_space) { p = drop_whitespace(p, end); @@ -73,13 +97,23 @@ const char *parse_float( if (res.ec == std::errc::invalid_argument || res.ec == std::errc::result_out_of_range) { dst = fallback; } + else if (require_trailing_space && res.ptr < end && !is_whitespace(*res.ptr)) { + /* If there are trailing non-space characters, do not eat up the number. */ + dst = fallback; + return p; + } return res.ptr; } -const char *parse_floats(const char *p, const char *end, float fallback, float *dst, int count) +const char *parse_floats(const char *p, + const char *end, + float fallback, + float *dst, + int count, + bool require_trailing_space) { for (int i = 0; i < count; ++i) { - p = parse_float(p, end, fallback, dst[i]); + p = parse_float(p, end, fallback, dst[i], true, require_trailing_space); } return p; } diff --git a/source/blender/io/wavefront_obj/importer/obj_import_string_utils.hh b/source/blender/io/wavefront_obj/importer/obj_import_string_utils.hh index 3f428b1ab5c..e42f5080d25 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_string_utils.hh +++ b/source/blender/io/wavefront_obj/importer/obj_import_string_utils.hh @@ -6,9 +6,6 @@ /* * Various text parsing utilities used by OBJ importer. - * The utilities are not directly usable by other formats, since - * they treat backslash (\) as a whitespace character (OBJ format - * allows backslashes to function as a line-continuation character). * * Many of these functions take two pointers (p, end) indicating * which part of a string to operate on, and return a possibly @@ -27,21 +24,22 @@ namespace blender::io::obj { * The returned line will not have '\n' characters at the end; * the `buffer` is modified to contain remaining text without * the input line. - * - * Note that backslash (\) character is treated as a line - * continuation. */ StringRef read_next_line(StringRef &buffer); /** + * Fix up OBJ line continuations by replacing backslash (\) and the + * following newline with spaces. + */ +void fixup_line_continuations(char *p, char *end); + +/** * Drop leading white-space from a string part. - * Note that backslash character is considered white-space. */ const char *drop_whitespace(const char *p, const char *end); /** * Drop leading non-white-space from a string part. - * Note that backslash character is considered white-space. */ const char *drop_non_whitespace(const char *p, const char *end); @@ -62,12 +60,17 @@ const char *parse_int( * The parsed result is stored in `dst`. The function skips * leading white-space unless `skip_space=false`. If the * number can't be parsed (invalid syntax, out of range), - * `fallback` value is stored instead. + * `fallback` value is stored instead. If `require_trailing_space` + * is true, the character after the number has to be whitespace. * * Returns the start of remainder of the input string after parsing. */ -const char *parse_float( - const char *p, const char *end, float fallback, float &dst, bool skip_space = true); +const char *parse_float(const char *p, + const char *end, + float fallback, + float &dst, + bool skip_space = true, + bool require_trailing_space = false); /** * Parse a number of white-space separated floats from an input string. @@ -77,6 +80,11 @@ const char *parse_float( * * Returns the start of remainder of the input string after parsing. */ -const char *parse_floats(const char *p, const char *end, float fallback, float *dst, int count); +const char *parse_floats(const char *p, + const char *end, + float fallback, + float *dst, + int count, + bool require_trailing_space = false); } // namespace blender::io::obj diff --git a/source/blender/io/wavefront_obj/importer/obj_importer.cc b/source/blender/io/wavefront_obj/importer/obj_importer.cc index f2051d195c8..47d7a9e2b27 100644 --- a/source/blender/io/wavefront_obj/importer/obj_importer.cc +++ b/source/blender/io/wavefront_obj/importer/obj_importer.cc @@ -9,6 +9,7 @@ #include "BLI_map.hh" #include "BLI_math_vec_types.hh" #include "BLI_set.hh" +#include "BLI_sort.hh" #include "BLI_string_ref.hh" #include "BKE_layer.h" @@ -18,6 +19,7 @@ #include "DNA_collection_types.h" +#include "obj_export_mtl.hh" #include "obj_import_file_reader.hh" #include "obj_import_mesh.hh" #include "obj_import_nurbs.hh" @@ -38,12 +40,20 @@ static void geometry_to_blender_objects(Main *bmain, Map<std::string, std::unique_ptr<MTLMaterial>> &materials, Map<std::string, Material *> &created_materials) { - BKE_view_layer_base_deselect_all(view_layer); LayerCollection *lc = BKE_layer_collection_get_active(view_layer); /* Don't do collection syncs for each object, will do once after the loop. */ BKE_layer_collection_resync_forbid(); + /* Sort objects by name: creating many objects is much faster if the creation + * order is sorted by name. */ + blender::parallel_sort( + all_geometries.begin(), all_geometries.end(), [](const auto &a, const auto &b) { + const char *na = a ? a->geometry_name_.c_str() : ""; + const char *nb = b ? b->geometry_name_.c_str() : ""; + return BLI_strcasecmp(na, nb) < 0; + }); + /* Create all the objects. */ Vector<Object *> objects; objects.reserve(all_geometries.size()); @@ -112,6 +122,9 @@ void importer_main(Main *bmain, mtl_parser.parse_and_store(materials); } + if (import_params.clear_selection) { + BKE_view_layer_base_deselect_all(view_layer); + } geometry_to_blender_objects(bmain, scene, view_layer, diff --git a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc index 6aec848573f..0fd711bdac6 100644 --- a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc +++ b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc @@ -60,7 +60,7 @@ TEST_F(obj_exporter_test, filter_objects_curves_as_mesh) return; } auto [objmeshes, objcurves]{filter_supported_objects(depsgraph, _export.params)}; - EXPECT_EQ(objmeshes.size(), 20); + EXPECT_EQ(objmeshes.size(), 21); EXPECT_EQ(objcurves.size(), 0); } @@ -185,17 +185,17 @@ TEST(obj_exporter_writer, mtllib) TEST(obj_exporter_writer, format_handler_buffer_chunking) { /* Use a tiny buffer chunk size, so that the test below ends up creating several blocks. */ - FormatHandler<eFileType::OBJ, 16> h; - h.write<eOBJSyntaxElement::object_name>("abc"); - h.write<eOBJSyntaxElement::object_name>("abcd"); - h.write<eOBJSyntaxElement::object_name>("abcde"); - h.write<eOBJSyntaxElement::object_name>("abcdef"); - h.write<eOBJSyntaxElement::object_name>("012345678901234567890123456789abcd"); - h.write<eOBJSyntaxElement::object_name>("123"); - h.write<eOBJSyntaxElement::curve_element_begin>(); - h.write<eOBJSyntaxElement::new_line>(); - h.write<eOBJSyntaxElement::nurbs_parameter_begin>(); - h.write<eOBJSyntaxElement::new_line>(); + FormatHandler h(16); + h.write_obj_object("abc"); + h.write_obj_object("abcd"); + h.write_obj_object("abcde"); + h.write_obj_object("abcdef"); + h.write_obj_object("012345678901234567890123456789abcd"); + h.write_obj_object("123"); + h.write_obj_curve_begin(); + h.write_obj_newline(); + h.write_obj_nurbs_parm_begin(); + h.write_obj_newline(); size_t got_blocks = h.get_block_count(); ASSERT_EQ(got_blocks, 7); diff --git a/source/blender/io/wavefront_obj/tests/obj_import_string_utils_tests.cc b/source/blender/io/wavefront_obj/tests/obj_import_string_utils_tests.cc index 46e093bb8a7..dc1cfd2b449 100644 --- a/source/blender/io/wavefront_obj/tests/obj_import_string_utils_tests.cc +++ b/source/blender/io/wavefront_obj/tests/obj_import_string_utils_tests.cc @@ -10,17 +10,34 @@ namespace blender::io::obj { TEST(obj_import_string_utils, read_next_line) { - std::string str = "abc\n \n\nline with \\\ncontinuation\nCRLF ending:\r\na"; + std::string str = "abc\n \n\nline with \t spaces\nCRLF ending:\r\na"; StringRef s = str; EXPECT_STRREF_EQ("abc", read_next_line(s)); EXPECT_STRREF_EQ(" ", read_next_line(s)); EXPECT_STRREF_EQ("", read_next_line(s)); - EXPECT_STRREF_EQ("line with \\\ncontinuation", read_next_line(s)); + EXPECT_STRREF_EQ("line with \t spaces", read_next_line(s)); EXPECT_STRREF_EQ("CRLF ending:\r", read_next_line(s)); EXPECT_STRREF_EQ("a", read_next_line(s)); EXPECT_TRUE(s.is_empty()); } +TEST(obj_import_string_utils, fixup_line_continuations) +{ + const char *str = + "backslash \\\n eol\n" + "backslash spaces \\ \n eol\n" + "without eol \\ is \\\\ \\ left intact\n" + "\\"; + const char *exp = + "backslash eol\n" + "backslash spaces eol\n" + "without eol \\ is \\\\ \\ left intact\n" + "\\"; + std::string buf(str); + fixup_line_continuations(buf.data(), buf.data() + buf.size()); + EXPECT_STRREF_EQ(exp, buf); +} + static StringRef drop_whitespace(StringRef s) { return StringRef(drop_whitespace(s.begin(), s.end()), s.end()); @@ -29,9 +46,14 @@ static StringRef parse_int(StringRef s, int fallback, int &dst, bool skip_space { return StringRef(parse_int(s.begin(), s.end(), fallback, dst, skip_space), s.end()); } -static StringRef parse_float(StringRef s, float fallback, float &dst, bool skip_space = true) +static StringRef parse_float(StringRef s, + float fallback, + float &dst, + bool skip_space = true, + bool require_trailing_space = false) { - return StringRef(parse_float(s.begin(), s.end(), fallback, dst, skip_space), s.end()); + return StringRef( + parse_float(s.begin(), s.end(), fallback, dst, skip_space, require_trailing_space), s.end()); } TEST(obj_import_string_utils, drop_whitespace) @@ -49,7 +71,7 @@ TEST(obj_import_string_utils, drop_whitespace) /* No leading whitespace */ EXPECT_STRREF_EQ("c", drop_whitespace("c")); /* Case with backslash, should be treated as whitespace */ - EXPECT_STRREF_EQ("d", drop_whitespace(" \\ d")); + EXPECT_STRREF_EQ("d", drop_whitespace(" \t d")); } TEST(obj_import_string_utils, parse_int_valid) @@ -126,6 +148,9 @@ TEST(obj_import_string_utils, parse_float_invalid) /* Has leading white-space when we don't expect it */ EXPECT_STRREF_EQ(" 1", parse_float(" 1", -4.0f, val, false)); EXPECT_EQ(val, -4.0f); + /* Has trailing non-number characters when we don't want them */ + EXPECT_STRREF_EQ("123.5.png", parse_float(" 123.5.png", -5.0f, val, true, true)); + EXPECT_EQ(val, -5.0f); } } // namespace blender::io::obj 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 b67adbc9753..99e12aed99c 100644 --- a/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc +++ b/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc @@ -8,11 +8,12 @@ #include "BKE_curve.h" #include "BKE_customdata.h" #include "BKE_main.h" +#include "BKE_mesh.h" #include "BKE_object.h" #include "BKE_scene.h" #include "BLI_listbase.h" -#include "BLI_math_base.h" +#include "BLI_math_base.hh" #include "BLI_math_vec_types.hh" #include "BLO_readfile.h" @@ -47,7 +48,8 @@ class obj_importer_test : public BlendfileLoadingBaseTest { void import_and_check(const char *path, const Expectation *expect, size_t expect_count, - int expect_mat_count) + int expect_mat_count, + int expect_image_count = 0) { if (!blendfile_load("io_tests/blend_geometry/all_quads.blend")) { ADD_FAILURE(); @@ -58,6 +60,10 @@ class obj_importer_test : public BlendfileLoadingBaseTest { 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); @@ -90,8 +96,9 @@ class obj_importer_test : public BlendfileLoadingBaseTest { EXPECT_EQ(mesh->totedge, exp.mesh_totedge_or_curve_endp); EXPECT_EQ(mesh->totpoly, exp.mesh_totpoly_or_curve_order); EXPECT_EQ(mesh->totloop, exp.mesh_totloop_or_curve_cyclic); - EXPECT_V3_NEAR(mesh->mvert[0].co, exp.vert_first, 0.0001f); - EXPECT_V3_NEAR(mesh->mvert[mesh->totvert - 1].co, exp.vert_last, 0.0001f); + const Span<MVert> verts = mesh->verts(); + EXPECT_V3_NEAR(verts.first().co, exp.vert_first, 0.0001f); + EXPECT_V3_NEAR(verts.last().co, exp.vert_last, 0.0001f); const float3 *lnors = (const float3 *)(CustomData_get_layer(&mesh->ldata, CD_NORMAL)); float3 normal_first = lnors != nullptr ? lnors[0] : float3(0, 0, 0); EXPECT_V3_NEAR(normal_first, exp.normal_first, 0.0001f); @@ -130,12 +137,12 @@ class obj_importer_test : public BlendfileLoadingBaseTest { DEG_OBJECT_ITER_END; EXPECT_EQ(object_index, expect_count); - /* Count number of materials. */ - int mat_count = 0; - LISTBASE_FOREACH (ID *, id, &bfile->main->materials) { - ++mat_count; - } + /* Check number of materials & textures. */ + const int mat_count = BLI_listbase_count(&bfile->main->materials); EXPECT_EQ(mat_count, expect_mat_count); + + const int ima_count = BLI_listbase_count(&bfile->main->images); + EXPECT_EQ(ima_count, expect_image_count); } }; @@ -156,6 +163,36 @@ TEST_F(obj_importer_test, import_cube) import_and_check("cube.obj", expect, std::size(expect), 1); } +TEST_F(obj_importer_test, import_cube_o_after_verts) +{ + Expectation expect[] = { + {"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)}, + { + "OBActualCube", + OB_MESH, + 8, + 12, + 6, + 24, + float3(-1, -1, 1), + float3(1, -1, -1), + float3(0, 0, 1), + }, + { + "OBSparseTri", + OB_MESH, + 3, + 3, + 1, + 3, + float3(1, -1, 1), + float3(-2, -2, 2), + float3(-0.2357f, 0.9428f, 0.2357f), + }, + }; + import_and_check("cube_o_after_verts.obj", expect, std::size(expect), 2); +} + TEST_F(obj_importer_test, import_suzanne_all_data) { Expectation expect[] = { @@ -194,7 +231,9 @@ TEST_F(obj_importer_test, import_nurbs_curves) { Expectation expect[] = { {"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)}, + {"OBCurveDeg3", OB_CURVES_LEGACY, 4, 0, 3, 0, float3(10, -2, 0), float3(6, -2, 0)}, {"OBnurbs_curves", OB_CURVES_LEGACY, 4, 0, 4, 0, float3(2, -2, 0), float3(-2, -2, 0)}, + {"OBNurbsCurveCyclic", OB_CURVES_LEGACY, 7, 0, 4, 1, float3(-2, -2, 0), float3(-6, 2, 0)}, {"OBNurbsCurveDiffWeights", OB_CURVES_LEGACY, 4, @@ -203,7 +242,6 @@ TEST_F(obj_importer_test, import_nurbs_curves) 0, float3(6, -2, 0), float3(2, -2, 0)}, - {"OBNurbsCurveCyclic", OB_CURVES_LEGACY, 7, 0, 4, 1, float3(-2, -2, 0), float3(-6, 2, 0)}, {"OBNurbsCurveEndpoint", OB_CURVES_LEGACY, 4, @@ -212,7 +250,6 @@ TEST_F(obj_importer_test, import_nurbs_curves) 0, float3(-6, -2, 0), float3(-10, -2, 0)}, - {"OBCurveDeg3", OB_CURVES_LEGACY, 4, 0, 3, 0, float3(10, -2, 0), float3(6, -2, 0)}, }; import_and_check("nurbs_curves.obj", expect, std::size(expect), 0); } @@ -237,7 +274,8 @@ TEST_F(obj_importer_test, import_nurbs_manual) { Expectation expect[] = { {"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)}, - {"OBCurve_Uniform_Parm", OB_CURVES_LEGACY, 5, 0, 4, 0, float3(-2, 0, 2), float3(-2, 0, 2)}, + {"OBCurve_Cyclic", OB_CURVES_LEGACY, 7, 0, 4, 1, float3(-2, 0, 2), float3(2, 0, -2)}, + {"OBCurve_Endpoints", OB_CURVES_LEGACY, 5, 1, 4, 0, float3(-2, 0, 2), float3(-2, 0, 2)}, {"OBCurve_NonUniform_Parm", OB_CURVES_LEGACY, 5, @@ -246,8 +284,7 @@ TEST_F(obj_importer_test, import_nurbs_manual) 0, float3(-2, 0, 2), float3(-2, 0, 2)}, - {"OBCurve_Endpoints", OB_CURVES_LEGACY, 5, 1, 4, 0, float3(-2, 0, 2), float3(-2, 0, 2)}, - {"OBCurve_Cyclic", OB_CURVES_LEGACY, 7, 0, 4, 1, float3(-2, 0, 2), float3(2, 0, -2)}, + {"OBCurve_Uniform_Parm", OB_CURVES_LEGACY, 5, 0, 4, 0, float3(-2, 0, 2), float3(-2, 0, 2)}, }; import_and_check("nurbs_manual.obj", expect, std::size(expect), 0); } @@ -256,7 +293,7 @@ TEST_F(obj_importer_test, import_nurbs_mesh) { Expectation expect[] = { {"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)}, - {"OBTorus Knot", + {"OBTorus_Knot", OB_MESH, 108, 108, @@ -274,30 +311,69 @@ TEST_F(obj_importer_test, import_materials) {"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)}, {"OBmaterials", OB_MESH, 8, 12, 6, 24, float3(-1, -1, 1), float3(1, -1, -1)}, }; - import_and_check("materials.obj", expect, std::size(expect), 4); + import_and_check("materials.obj", expect, std::size(expect), 4, 8); } -TEST_F(obj_importer_test, import_faces_invalid_or_with_holes) +TEST_F(obj_importer_test, import_cubes_with_textures_rel) { Expectation expect[] = { {"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)}, - {"OBFaceWithHole_BecomesTwoFacesFormingAHole", + {"OBCube4Tex", OB_MESH, 8, - 10, - 2, 12, - float3(-2, 0, -2), - float3(1, 0, -1)}, - {"OBFaceQuadDupSomeVerts_BecomesOneQuadUsing4Verts", + 6, + 24, + float3(1, 1, -1), + float3(-1, -1, 1), + float3(0, 1, 0), + float2(0.9935f, 0.0020f)}, + {"OBCubeTexMul", OB_MESH, 8, - 4, + 12, + 6, + 24, + float3(4, -2, -1), + float3(2, -4, 1), + float3(0, 1, 0), + float2(0.9935f, 0.0020f)}, + {"OBCubeTiledTex", + OB_MESH, + 8, + 12, + 6, + 24, + float3(4, 1, -1), + float3(2, -1, 1), + float3(0, 1, 0), + float2(0.9935f, 0.0020f)}, + {"OBCubeTiledTexFromAnotherFolder", + OB_MESH, + 8, + 12, + 6, + 24, + float3(7, 1, -1), + float3(5, -1, 1), + float3(0, 1, 0), + float2(0.9935f, 0.0020f)}, + }; + import_and_check("cubes_with_textures_rel.obj", expect, std::size(expect), 4, 4); +} + +TEST_F(obj_importer_test, import_faces_invalid_or_with_holes) +{ + Expectation expect[] = { + {"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)}, + {"OBFaceAllVerts_BecomesOneOverlappingFaceUsingAllVerts", + OB_MESH, + 8, + 8, 1, - 4, - float3(3, 0, -2), - float3(6, 0, -1)}, - {"OBFaceTriDupVert_Becomes1Tri", OB_MESH, 8, 3, 1, 3, float3(-2, 0, 3), float3(1, 0, 4)}, + 8, + float3(8, 0, -2), + float3(11, 0, -1)}, {"OBFaceAllVertsDup_BecomesOneOverlappingFaceUsingAllVerts", OB_MESH, 8, @@ -306,15 +382,24 @@ TEST_F(obj_importer_test, import_faces_invalid_or_with_holes) 8, float3(3, 0, 3), float3(6, 0, 4)}, - {"OBFaceAllVerts_BecomesOneOverlappingFaceUsingAllVerts", + {"OBFaceJustTwoVerts_IsSkipped", OB_MESH, 2, 0, 0, 0, float3(8, 0, 3), float3(8, 0, 7)}, + {"OBFaceQuadDupSomeVerts_BecomesOneQuadUsing4Verts", OB_MESH, - 8, - 8, + 4, + 4, 1, + 4, + float3(3, 0, -2), + float3(7, 0, -2)}, + {"OBFaceTriDupVert_Becomes1Tri", OB_MESH, 3, 3, 1, 3, float3(-2, 0, 3), float3(2, 0, 7)}, + {"OBFaceWithHole_BecomesTwoFacesFormingAHole", + OB_MESH, 8, - float3(8, 0, -2), - float3(11, 0, -1)}, - {"OBFaceJustTwoVerts_IsSkipped", OB_MESH, 8, 0, 0, 0, float3(8, 0, 3), float3(11, 0, 4)}, + 10, + 2, + 12, + float3(-2, 0, -2), + float3(1, 0, -1)}, }; import_and_check("faces_invalid_or_with_holes.obj", expect, std::size(expect), 0); } @@ -325,12 +410,12 @@ TEST_F(obj_importer_test, import_invalid_indices) {"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)}, {"OBQuad", OB_MESH, - 4, + 3, 3, 1, 3, float3(-2, 0, -2), - float3(2, 0, -2), + float3(2, 0, 2), float3(0, 1, 0), float2(0.5f, 0.25f)}, }; @@ -343,12 +428,12 @@ TEST_F(obj_importer_test, import_invalid_syntax) {"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)}, {"OBObjectWithAReallyLongNameToCheckHowImportHandlesNamesThatAreLon", OB_MESH, - 10, /* NOTE: right now parses some invalid obj syntax as valid vertices. */ + 3, 3, 1, 3, float3(1, 2, 3), - float3(10, 11, 12), + float3(7, 8, 9), float3(0, 1, 0), float2(0.5f, 0.25f)}, }; @@ -360,6 +445,63 @@ TEST_F(obj_importer_test, import_all_objects) Expectation expect[] = { {"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)}, /* .obj file has empty EmptyText and EmptyMesh objects; these are ignored and skipped */ + {"OBBezierCurve", OB_MESH, 13, 12, 0, 0, float3(-1, -2, 0), float3(1, -2, 0)}, + {"OBBlankCube", OB_MESH, 8, 13, 7, 26, float3(1, 1, -1), float3(-1, 1, 1), float3(0, 0, 1)}, + {"OBMaterialCube", + OB_MESH, + 8, + 13, + 7, + 26, + float3(28, 1, -1), + float3(26, 1, 1), + float3(-1, 0, 0)}, + {"OBNurbsCircle", + OB_MESH, + 96, + 96, + 0, + 0, + float3(3.292893f, -2.707107f, 0), + float3(3.369084f, -2.77607f, 0)}, + {"OBNurbsCircle.001", OB_MESH, 4, 4, 0, 0, float3(2, -3, 0), float3(3, -2, 0)}, + {"OBParticleCube", + OB_MESH, + 8, + 13, + 7, + 26, + float3(22, 1, -1), + float3(20, 1, 1), + float3(0, 0, 1)}, + {"OBShapeKeyCube", + OB_MESH, + 8, + 13, + 7, + 26, + float3(19, 1, -1), + float3(17, 1, 1), + float3(-0.4082f, -0.4082f, 0.8165f)}, + {"OBSmoothCube", + OB_MESH, + 8, + 13, + 7, + 26, + float3(4, 1, -1), + float3(2, 1, 1), + float3(0.5774f, 0.5773f, 0.5774f)}, + {"OBSurface", + OB_MESH, + 256, + 480, + 224, + 896, + float3(7.292893f, -2.707107f, -1), + float3(7.525872f, -2.883338f, 1), + float3(-0.7071f, -0.7071f, 0), + float2(0, 0.142857f)}, {"OBSurfPatch", OB_MESH, 256, @@ -380,24 +522,16 @@ TEST_F(obj_importer_test, import_all_objects) float3(11, -2, 1), float3(-0.0541f, -0.0541f, -0.9971f), float2(0, 1)}, - {"OBSmoothCube", - OB_MESH, - 8, - 13, - 7, - 26, - float3(4, 1, -1), - float3(2, 1, 1), - float3(0.5774f, 0.5773f, 0.5774f)}, - {"OBMaterialCube", + {"OBSurfTorus.001", OB_MESH, - 8, - 13, - 7, - 26, - float3(28, 1, -1), - float3(26, 1, 1), - float3(-1, 0, 0)}, + 1024, + 2048, + 1024, + 4096, + float3(5.34467f, -2.65533f, -0.176777f), + float3(5.232792f, -2.411795f, -0.220835f), + float3(-0.5042f, -0.5042f, -0.7011f), + float2(0, 1)}, {"OBTaperCube", OB_MESH, 106, @@ -407,24 +541,26 @@ TEST_F(obj_importer_test, import_all_objects) float3(24.444445f, 0.502543f, -0.753814f), float3(23.790743f, 0.460522f, -0.766546f), float3(-0.0546f, 0.1716f, 0.9837f)}, - {"OBParticleCube", + {"OBText", OB_MESH, - 8, - 13, - 7, - 26, - float3(22, 1, -1), - float3(20, 1, 1), - float3(0, 0, 1)}, - {"OBShapeKeyCube", + 177, + 345, + 171, + 513, + float3(1.75f, -9.458f, 0), + float3(0.587f, -9.406f, 0), + float3(0, 0, 1), + float2(0.017544f, 0)}, + {"OBUVCube", OB_MESH, 8, 13, 7, 26, - float3(19, 1, -1), - float3(17, 1, 1), - float3(-0.4082f, -0.4082f, 0.8165f)}, + float3(7, 1, -1), + float3(5, 1, 1), + float3(0, 0, 1), + float2(0.654526f, 0.579873f)}, {"OBUVImageCube", OB_MESH, 8, @@ -435,15 +571,6 @@ TEST_F(obj_importer_test, import_all_objects) float3(8, 1, 1), float3(0, 0, 1), float2(0.654526f, 0.579873f)}, - {"OBVGroupCube", - OB_MESH, - 8, - 13, - 7, - 26, - float3(16, 1, -1), - float3(14, 1, 1), - float3(0, 0, 1)}, {"OBVColCube", OB_MESH, 8, @@ -455,57 +582,15 @@ TEST_F(obj_importer_test, import_all_objects) float3(0, 0, 1), float2(0, 0), float4(0.0f, 0.002125f, 1.0f, 1.0f)}, - {"OBUVCube", + {"OBVGroupCube", OB_MESH, 8, 13, 7, 26, - float3(7, 1, -1), - float3(5, 1, 1), - float3(0, 0, 1), - float2(0.654526f, 0.579873f)}, - {"OBNurbsCircle.001", OB_MESH, 4, 4, 0, 0, float3(2, -3, 0), float3(3, -2, 0)}, - {"OBSurface", - OB_MESH, - 256, - 480, - 224, - 896, - float3(7.292893f, -2.707107f, -1), - float3(7.525872f, -2.883338f, 1), - float3(-0.7071f, -0.7071f, 0), - float2(0, 0.142857f)}, - {"OBText", - OB_MESH, - 177, - 345, - 171, - 513, - float3(1.75f, -9.458f, 0), - float3(0.587f, -9.406f, 0), - float3(0, 0, 1), - float2(0.017544f, 0)}, - {"OBSurfTorus.001", - OB_MESH, - 1024, - 2048, - 1024, - 4096, - float3(5.34467f, -2.65533f, -0.176777f), - float3(5.232792f, -2.411795f, -0.220835f), - float3(-0.5042f, -0.5042f, -0.7011f), - float2(0, 1)}, - {"OBNurbsCircle", - OB_MESH, - 96, - 96, - 0, - 0, - float3(3.292893f, -2.707107f, 0), - float3(3.369084f, -2.77607f, 0)}, - {"OBBezierCurve", OB_MESH, 13, 12, 0, 0, float3(-1, -2, 0), float3(1, -2, 0)}, - {"OBBlankCube", OB_MESH, 8, 13, 7, 26, float3(1, 1, -1), float3(-1, 1, 1), float3(0, 0, 1)}, + float3(16, 1, -1), + float3(14, 1, 1), + float3(0, 0, 1)}, }; import_and_check("all_objects.obj", expect, std::size(expect), 7); } @@ -514,28 +599,6 @@ TEST_F(obj_importer_test, import_cubes_vertex_colors) { Expectation expect[] = { {"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)}, - {"OBCubeVertexByte", - OB_MESH, - 8, - 12, - 6, - 24, - float3(1.0f, 1.0f, -1.0f), - float3(-1.0f, -1.0f, 1.0f), - float3(0, 0, 0), - float2(0, 0), - float4(0.846873f, 0.027321f, 0.982123f, 1.0f)}, - {"OBCubeVertexFloat", - OB_MESH, - 8, - 12, - 6, - 24, - float3(3.392028f, 1.0f, -1.0f), - float3(1.392028f, -1.0f, 1.0f), - float3(0, 0, 0), - float2(0, 0), - float4(49.99467f, 0.027321f, 0.982123f, 1.0f)}, {"OBCubeCornerByte", OB_MESH, 8, @@ -577,36 +640,91 @@ TEST_F(obj_importer_test, import_cubes_vertex_colors) 24, float3(-4.550208f, -1.0f, -1.918042f), float3(-2.550208f, 1.0f, -3.918042f)}, + {"OBCubeVertexByte", + OB_MESH, + 8, + 12, + 6, + 24, + float3(1.0f, 1.0f, -1.0f), + float3(-1.0f, -1.0f, 1.0f), + float3(0, 0, 0), + float2(0, 0), + float4(0.846873f, 0.027321f, 0.982123f, 1.0f)}, + {"OBCubeVertexFloat", + OB_MESH, + 8, + 12, + 6, + 24, + float3(3.392028f, 1.0f, -1.0f), + float3(1.392028f, -1.0f, 1.0f), + float3(0, 0, 0), + float2(0, 0), + float4(49.99467f, 0.027321f, 0.982123f, 1.0f)}, }; import_and_check("cubes_vertex_colors.obj", expect, std::size(expect), 0); } TEST_F(obj_importer_test, import_cubes_vertex_colors_mrgb) { - Expectation expect[] = {{"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)}, - {"OBCubeXYZRGB", - OB_MESH, - 8, - 12, - 6, - 24, - float3(1, 1, -1), - float3(-1, -1, 1), - float3(0, 0, 0), - float2(0, 0), - float4(0.6038f, 0.3185f, 0.1329f, 1.0f)}, - {"OBCubeMRGB", - OB_MESH, - 8, - 12, - 6, - 24, - float3(4, 1, -1), - float3(2, -1, 1), - float3(0, 0, 0), - float2(0, 0), - float4(0.8714f, 0.6308f, 0.5271f, 1.0f)}}; + Expectation expect[] = { + {"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)}, + {"OBCubeMRGB", + OB_MESH, + 8, + 12, + 6, + 24, + float3(4, 1, -1), + float3(2, -1, 1), + float3(0, 0, 0), + float2(0, 0), + float4(0.8714f, 0.6308f, 0.5271f, 1.0f)}, + {"OBCubeXYZRGB", + OB_MESH, + 8, + 12, + 6, + 24, + float3(1, 1, -1), + float3(-1, -1, 1), + float3(0, 0, 0), + float2(0, 0), + float4(0.6038f, 0.3185f, 0.1329f, 1.0f)}, + {"OBTriMRGB", + OB_MESH, + 3, + 3, + 1, + 3, + float3(12, 1, -1), + float3(10, 0, -1), + float3(0, 0, 0), + float2(0, 0), + float4(1.0f, 0.0f, 0.0f, 1.0f)}, + { + "OBTriNoColors", + OB_MESH, + 3, + 3, + 1, + 3, + float3(8, 1, -1), + float3(6, 0, -1), + }, + }; import_and_check("cubes_vertex_colors_mrgb.obj", expect, std::size(expect), 0); } +TEST_F(obj_importer_test, import_vertices) +{ + Expectation expect[] = { + {"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)}, + /* Loose vertices without faces or edges. */ + {"OBCube.001", OB_MESH, 8, 0, 0, 0, float3(1, 1, -1), float3(-1, 1, 1)}, + }; + import_and_check("vertices.obj", expect, std::size(expect), 0); +} + } // namespace blender::io::obj diff --git a/source/blender/io/wavefront_obj/tests/obj_mtl_parser_tests.cc b/source/blender/io/wavefront_obj/tests/obj_mtl_parser_tests.cc index 068cdc0bf3a..5691aa5bea1 100644 --- a/source/blender/io/wavefront_obj/tests/obj_mtl_parser_tests.cc +++ b/source/blender/io/wavefront_obj/tests/obj_mtl_parser_tests.cc @@ -6,6 +6,7 @@ #include "testing/testing.h" +#include "obj_export_mtl.hh" #include "obj_import_file_reader.hh" namespace blender::io::obj { @@ -58,9 +59,9 @@ class obj_mtl_parser_test : public testing::Test { EXPECT_NEAR(exp.d, got.d, tol); EXPECT_NEAR(exp.map_Bump_strength, got.map_Bump_strength, tol); EXPECT_EQ(exp.illum, got.illum); - for (const auto &it : exp.texture_maps.items()) { - const tex_map_XX &exp_tex = it.value; - const tex_map_XX &got_tex = got.texture_maps.lookup(it.key); + for (int key = 0; key < (int)MTLTexMapType::Count; key++) { + const MTLTexMap &exp_tex = exp.texture_maps[key]; + const MTLTexMap &got_tex = got.texture_maps[key]; EXPECT_STREQ(exp_tex.image_path.c_str(), got_tex.image_path.c_str()); EXPECT_V3_NEAR(exp_tex.translation, got_tex.translation, tol); EXPECT_V3_NEAR(exp_tex.scale, got_tex.scale, tol); @@ -113,8 +114,8 @@ TEST_F(obj_mtl_parser_test, string_newlines_whitespace) mat[4].Kd = {0.6f, 0.7f, 0.8f}; mat[5].name = "crlf_ending"; mat[5].Ns = 5.0f; - mat[5].tex_map_of_type(eMTLSyntaxElement::map_Kd).image_path = "sometex_d.png"; - mat[5].tex_map_of_type(eMTLSyntaxElement::map_Ks).image_path = "sometex_s_spaces_after_name.png"; + mat[5].tex_map_of_type(MTLTexMapType::Kd).image_path = "sometex_d.png"; + mat[5].tex_map_of_type(MTLTexMapType::Ks).image_path = "sometex_s_spaces_after_name.png"; check_string(text, mat, ARRAY_SIZE(mat)); } @@ -158,7 +159,7 @@ TEST_F(obj_mtl_parser_test, all_objects) TEST_F(obj_mtl_parser_test, materials) { - MTLMaterial mat[5]; + MTLMaterial mat[6]; mat[0].name = "no_textures_red"; mat[0].Ka = {0.3f, 0.3f, 0.3f}; mat[0].Kd = {0.8f, 0.3f, 0.1f}; @@ -175,13 +176,13 @@ TEST_F(obj_mtl_parser_test, materials) mat[1].illum = 2; mat[1].map_Bump_strength = 1; { - tex_map_XX &kd = mat[1].tex_map_of_type(eMTLSyntaxElement::map_Kd); + MTLTexMap &kd = mat[1].tex_map_of_type(MTLTexMapType::Kd); kd.image_path = "texture.png"; - tex_map_XX &ns = mat[1].tex_map_of_type(eMTLSyntaxElement::map_Ns); + MTLTexMap &ns = mat[1].tex_map_of_type(MTLTexMapType::Ns); ns.image_path = "sometexture_Roughness.png"; - tex_map_XX &refl = mat[1].tex_map_of_type(eMTLSyntaxElement::map_refl); + MTLTexMap &refl = mat[1].tex_map_of_type(MTLTexMapType::refl); refl.image_path = "sometexture_Metallic.png"; - tex_map_XX &bump = mat[1].tex_map_of_type(eMTLSyntaxElement::map_Bump); + MTLTexMap &bump = mat[1].tex_map_of_type(MTLTexMapType::bump); bump.image_path = "sometexture_Normal.png"; } @@ -202,13 +203,13 @@ TEST_F(obj_mtl_parser_test, materials) mat[3].Ns = 800; mat[3].map_Bump_strength = 0.5f; { - tex_map_XX &kd = mat[3].tex_map_of_type(eMTLSyntaxElement::map_Kd); + MTLTexMap &kd = mat[3].tex_map_of_type(MTLTexMapType::Kd); kd.image_path = "someHatTexture_BaseColor.jpg"; - tex_map_XX &ns = mat[3].tex_map_of_type(eMTLSyntaxElement::map_Ns); + MTLTexMap &ns = mat[3].tex_map_of_type(MTLTexMapType::Ns); ns.image_path = "someHatTexture_Roughness.jpg"; - tex_map_XX &refl = mat[3].tex_map_of_type(eMTLSyntaxElement::map_refl); + MTLTexMap &refl = mat[3].tex_map_of_type(MTLTexMapType::refl); refl.image_path = "someHatTexture_Metalness.jpg"; - tex_map_XX &bump = mat[3].tex_map_of_type(eMTLSyntaxElement::map_Bump); + MTLTexMap &bump = mat[3].tex_map_of_type(MTLTexMapType::bump); bump.image_path = "someHatTexture_Normal.jpg"; } @@ -222,20 +223,34 @@ TEST_F(obj_mtl_parser_test, materials) mat[4].d = 0.5; mat[4].map_Bump_strength = 0.1f; { - tex_map_XX &kd = mat[4].tex_map_of_type(eMTLSyntaxElement::map_Kd); + MTLTexMap &kd = mat[4].tex_map_of_type(MTLTexMapType::Kd); kd.image_path = "sometex_d.png"; - tex_map_XX &ns = mat[4].tex_map_of_type(eMTLSyntaxElement::map_Ns); + MTLTexMap &ns = mat[4].tex_map_of_type(MTLTexMapType::Ns); ns.image_path = "sometex_ns.psd"; - tex_map_XX &refl = mat[4].tex_map_of_type(eMTLSyntaxElement::map_refl); + MTLTexMap &refl = mat[4].tex_map_of_type(MTLTexMapType::refl); refl.image_path = "clouds.tiff"; refl.scale = {1.5f, 2.5f, 3.5f}; refl.translation = {4.5f, 5.5f, 6.5f}; refl.projection_type = SHD_PROJ_SPHERE; - tex_map_XX &bump = mat[4].tex_map_of_type(eMTLSyntaxElement::map_Bump); + MTLTexMap &bump = mat[4].tex_map_of_type(MTLTexMapType::bump); bump.image_path = "somebump.tga"; bump.scale = {3, 4, 5}; } + mat[5].name = "Parser_ScaleOffset_Test"; + { + MTLTexMap &kd = mat[5].tex_map_of_type(MTLTexMapType::Kd); + kd.translation = {2.5f, 0.0f, 0.0f}; + kd.image_path = "OffsetOneValue.png"; + MTLTexMap &ks = mat[5].tex_map_of_type(MTLTexMapType::Ks); + ks.scale = {1.5f, 2.5f, 1.0f}; + ks.translation = {3.5f, 4.5f, 0.0f}; + ks.image_path = "ScaleOffsetBothTwovalues.png"; + MTLTexMap &ns = mat[5].tex_map_of_type(MTLTexMapType::Ns); + ns.scale = {0.5f, 1.0f, 1.0f}; + ns.image_path = "1.Value.png"; + } + check("materials.mtl", mat, ARRAY_SIZE(mat)); } |