Blender V4.3
abc_reader_mesh.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
9#include "abc_reader_mesh.h"
10#include "abc_axis_conversion.h"
11#include "abc_customdata.h"
12#include "abc_util.h"
13
15#include "DNA_material_types.h"
16#include "DNA_modifier_types.h"
17
18#include "DNA_object_types.h"
19
20#include "BLI_compiler_compat.h"
21#include "BLI_listbase.h"
22#include "BLI_map.hh"
23#include "BLI_math_vector.h"
24#include "BLI_ordered_edge.hh"
25
26#include "BLT_translation.hh"
27
28#include "BKE_customdata.hh"
29#include "BKE_geometry_set.hh"
30#include "BKE_lib_id.hh"
31#include "BKE_main.hh"
32#include "BKE_material.h"
33#include "BKE_mesh.hh"
34#include "BKE_object.hh"
35
36using Alembic::Abc::FloatArraySamplePtr;
37using Alembic::Abc::Int32ArraySamplePtr;
38using Alembic::Abc::P3fArraySamplePtr;
39using Alembic::Abc::PropertyHeader;
40using Alembic::Abc::V3fArraySamplePtr;
41
42using Alembic::AbcGeom::IC3fGeomParam;
43using Alembic::AbcGeom::IC4fGeomParam;
44using Alembic::AbcGeom::IFaceSet;
45using Alembic::AbcGeom::IFaceSetSchema;
46using Alembic::AbcGeom::IN3fGeomParam;
47using Alembic::AbcGeom::IObject;
48using Alembic::AbcGeom::IPolyMesh;
49using Alembic::AbcGeom::IPolyMeshSchema;
50using Alembic::AbcGeom::ISampleSelector;
51using Alembic::AbcGeom::ISubD;
52using Alembic::AbcGeom::ISubDSchema;
53using Alembic::AbcGeom::IV2fGeomParam;
54using Alembic::AbcGeom::kWrapExisting;
55using Alembic::AbcGeom::N3fArraySample;
56using Alembic::AbcGeom::N3fArraySamplePtr;
57using Alembic::AbcGeom::UInt32ArraySamplePtr;
58using Alembic::AbcGeom::V2fArraySamplePtr;
59
60namespace blender::io::alembic {
61
62/* NOTE: Alembic's face winding order is clockwise, to match with Renderman. */
63
64/* Some helpers for mesh generation */
65namespace utils {
66
67static std::map<std::string, Material *> build_material_map(const Main *bmain)
68{
69 std::map<std::string, Material *> mat_map;
70 LISTBASE_FOREACH (Material *, material, &bmain->materials) {
71 mat_map[material->id.name + 2] = material;
72 }
73 return mat_map;
74}
75
76static void assign_materials(Main *bmain,
77 Object *ob,
78 const std::map<std::string, int> &mat_index_map)
79{
80 std::map<std::string, int>::const_iterator it;
81 if (mat_index_map.size() > MAXMAT) {
82 return;
83 }
84
85 std::map<std::string, Material *> matname_to_material = build_material_map(bmain);
86 std::map<std::string, Material *>::iterator mat_iter;
87
88 for (it = mat_index_map.begin(); it != mat_index_map.end(); ++it) {
89 const std::string mat_name = it->first;
90 const int mat_index = it->second;
91
92 Material *assigned_mat;
93 mat_iter = matname_to_material.find(mat_name);
94 if (mat_iter == matname_to_material.end()) {
95 assigned_mat = BKE_material_add(bmain, mat_name.c_str());
96 id_us_min(&assigned_mat->id);
97 matname_to_material[mat_name] = assigned_mat;
98 }
99 else {
100 assigned_mat = mat_iter->second;
101 }
102
103 BKE_object_material_assign_single_obdata(bmain, ob, assigned_mat, mat_index);
104 }
105 if (ob->totcol > 0) {
106 ob->actcol = 1;
107 }
108}
109
110} /* namespace utils */
111
113 Int32ArraySamplePtr face_indices;
114 Int32ArraySamplePtr face_counts;
115
116 /* Optional settings for reading interpolated vertices. If present, `ceil_positions` has to be
117 * valid. */
118 std::optional<SampleInterpolationSettings> interpolation_settings;
119 P3fArraySamplePtr positions;
120 P3fArraySamplePtr ceil_positions;
121
123 V2fArraySamplePtr uvs;
124 UInt32ArraySamplePtr uvs_indices;
125};
126
127static void read_mverts_interp(float3 *vert_positions,
128 const P3fArraySamplePtr &positions,
129 const P3fArraySamplePtr &ceil_positions,
130 const double weight)
131{
132 float tmp[3];
133 for (int i = 0; i < positions->size(); i++) {
134 const Imath::V3f &floor_pos = (*positions)[i];
135 const Imath::V3f &ceil_pos = (*ceil_positions)[i];
136
137 interp_v3_v3v3(tmp, floor_pos.getValue(), ceil_pos.getValue(), float(weight));
138 copy_zup_from_yup(vert_positions[i], tmp);
139 }
140}
141
142static void read_mverts(CDStreamConfig &config, const AbcMeshData &mesh_data)
143{
144 float3 *vert_positions = config.positions;
145 const P3fArraySamplePtr &positions = mesh_data.positions;
146
147 if (mesh_data.interpolation_settings.has_value()) {
149 mesh_data.ceil_positions != nullptr,
150 "AbcMeshData does not have ceil positions although it has some interpolation settings.");
151
152 const double weight = mesh_data.interpolation_settings->weight;
153 read_mverts_interp(vert_positions, positions, mesh_data.ceil_positions, weight);
154 config.mesh->tag_positions_changed();
155 return;
156 }
157
158 read_mverts(*config.mesh, positions, nullptr);
159}
160
161void read_mverts(Mesh &mesh, const P3fArraySamplePtr positions, const N3fArraySamplePtr normals)
162{
163 MutableSpan<float3> vert_positions = mesh.vert_positions_for_write();
164 for (int i = 0; i < positions->size(); i++) {
165 Imath::V3f pos_in = (*positions)[i];
166
167 copy_zup_from_yup(vert_positions[i], pos_in.getValue());
168 }
169 mesh.tag_positions_changed();
170
171 if (normals) {
172 Vector<float3> vert_normals(mesh.verts_num);
173 for (const int64_t i : IndexRange(normals->size())) {
174 Imath::V3f nor_in = (*normals)[i];
175 copy_zup_from_yup(vert_normals[i], nor_in.getValue());
176 }
177 bke::mesh_vert_normals_assign(mesh, std::move(vert_normals));
178 }
179}
180
181static void read_mpolys(CDStreamConfig &config, const AbcMeshData &mesh_data)
182{
183 int *face_offsets = config.face_offsets;
184 int *corner_verts = config.corner_verts;
185 float2 *mloopuvs = config.mloopuv;
186
187 const Int32ArraySamplePtr &face_indices = mesh_data.face_indices;
188 const Int32ArraySamplePtr &face_counts = mesh_data.face_counts;
189 const V2fArraySamplePtr &uvs = mesh_data.uvs;
190 const size_t uvs_size = uvs == nullptr ? 0 : uvs->size();
191
192 const UInt32ArraySamplePtr &uvs_indices = mesh_data.uvs_indices;
193
194 const bool do_uvs = (mloopuvs && uvs && uvs_indices);
195 const bool do_uvs_per_loop = do_uvs && mesh_data.uv_scope == ABC_UV_SCOPE_LOOP;
196 BLI_assert(!do_uvs || mesh_data.uv_scope != ABC_UV_SCOPE_NONE);
197 uint loop_index = 0;
198 uint rev_loop_index = 0;
199 uint uv_index = 0;
200 bool seen_invalid_geometry = false;
201
202 for (int i = 0; i < face_counts->size(); i++) {
203 const int face_size = (*face_counts)[i];
204
205 face_offsets[i] = loop_index;
206
207 /* Polygons are always assumed to be smooth-shaded. If the Alembic mesh should be flat-shaded,
208 * this is encoded in custom loop normals. See #71246. */
209
210 /* NOTE: Alembic data is stored in the reverse order. */
211 rev_loop_index = loop_index + (face_size - 1);
212
213 uint last_vertex_index = 0;
214 for (int f = 0; f < face_size; f++, loop_index++, rev_loop_index--) {
215 const int vert = (*face_indices)[loop_index];
216 corner_verts[rev_loop_index] = vert;
217
218 if (f > 0 && vert == last_vertex_index) {
219 /* This face is invalid, as it has consecutive loops from the same vertex. This is caused
220 * by invalid geometry in the Alembic file, such as in #76514. */
221 seen_invalid_geometry = true;
222 }
223 last_vertex_index = vert;
224
225 if (do_uvs) {
226 uv_index = (*uvs_indices)[do_uvs_per_loop ? loop_index : last_vertex_index];
227
228 /* Some Alembic files are broken (or at least export UVs in a way we don't expect). */
229 if (uv_index >= uvs_size) {
230 continue;
231 }
232
233 mloopuvs[rev_loop_index][0] = (*uvs)[uv_index][0];
234 mloopuvs[rev_loop_index][1] = (*uvs)[uv_index][1];
235 }
236 }
237 }
238
239 bke::mesh_calc_edges(*config.mesh, false, false);
240 if (seen_invalid_geometry) {
241 if (config.modifier_error_message) {
242 *config.modifier_error_message = "Mesh hash invalid geometry; more details on the console";
243 }
244 BKE_mesh_validate(config.mesh, true, true);
245 }
246}
247
248static void process_no_normals(CDStreamConfig & /*config*/)
249{
250 /* Absence of normals in the Alembic mesh is interpreted as 'smooth'. */
251}
252
253static void process_loop_normals(CDStreamConfig &config, const N3fArraySamplePtr loop_normals_ptr)
254{
255 size_t loop_count = loop_normals_ptr->size();
256
257 if (loop_count == 0) {
258 process_no_normals(config);
259 return;
260 }
261
262 Mesh *mesh = config.mesh;
263 if (loop_count != mesh->corners_num) {
264 /* This happens in certain Houdini exports. When a mesh is animated and then replaced by a
265 * fluid simulation, Houdini will still write the original mesh's loop normals, but the mesh
266 * verts/loops/faces are from the simulation. In such cases the normals cannot be mapped to the
267 * mesh, so it's better to ignore them. */
268 process_no_normals(config);
269 return;
270 }
271
272 float(*lnors)[3] = static_cast<float(*)[3]>(
273 MEM_malloc_arrayN(loop_count, sizeof(float[3]), "ABC::FaceNormals"));
274
275 const OffsetIndices faces = mesh->faces();
276 const N3fArraySample &loop_normals = *loop_normals_ptr;
277 int abc_index = 0;
278 for (int i = 0, e = mesh->faces_num; i < e; i++) {
279 const IndexRange face = faces[i];
280 /* As usual, ABC orders the loops in reverse. */
281 for (int j = face.size() - 1; j >= 0; j--, abc_index++) {
282 int blender_index = face[j];
283 copy_zup_from_yup(lnors[blender_index], loop_normals[abc_index].getValue());
284 }
285 }
286
287 BKE_mesh_set_custom_normals(mesh, lnors);
288
289 MEM_freeN(lnors);
290}
291
293 const N3fArraySamplePtr vertex_normals_ptr)
294{
295 size_t normals_count = vertex_normals_ptr->size();
296 if (normals_count == 0) {
297 process_no_normals(config);
298 return;
299 }
300
301 float(*vert_normals)[3] = static_cast<float(*)[3]>(
302 MEM_malloc_arrayN(normals_count, sizeof(float[3]), "ABC::VertexNormals"));
303
304 const N3fArraySample &vertex_normals = *vertex_normals_ptr;
305 for (int index = 0; index < normals_count; index++) {
306 copy_zup_from_yup(vert_normals[index], vertex_normals[index].getValue());
307 }
308
309 BKE_mesh_set_custom_normals_from_verts(config.mesh, vert_normals);
310 MEM_freeN(vert_normals);
311}
312
313static void process_normals(CDStreamConfig &config,
314 const IN3fGeomParam &normals,
315 const ISampleSelector &selector)
316{
317 if (!normals.valid()) {
318 process_no_normals(config);
319 return;
320 }
321
322 IN3fGeomParam::Sample normsamp = normals.getExpandedValue(selector);
323 Alembic::AbcGeom::GeometryScope scope = normals.getScope();
324
325 switch (scope) {
326 case Alembic::AbcGeom::kFacevaryingScope: /* 'Vertex Normals' in Houdini. */
327 process_loop_normals(config, normsamp.getVals());
328 break;
329 case Alembic::AbcGeom::kVertexScope:
330 case Alembic::AbcGeom::kVaryingScope: /* 'Point Normals' in Houdini. */
331 process_vertex_normals(config, normsamp.getVals());
332 break;
333 case Alembic::AbcGeom::kConstantScope:
334 case Alembic::AbcGeom::kUniformScope:
335 case Alembic::AbcGeom::kUnknownScope:
336 process_no_normals(config);
337 break;
338 }
339}
340
342 AbcMeshData &abc_data,
343 const IV2fGeomParam &uv,
344 const ISampleSelector &selector)
345{
346 if (!uv.valid()) {
347 return;
348 }
349
350 IV2fGeomParam::Sample uvsamp;
351 uv.getIndexed(uvsamp, selector);
352
353 UInt32ArraySamplePtr uvs_indices = uvsamp.getIndices();
354
355 const AbcUvScope uv_scope = get_uv_scope(uv.getScope(), config, uvs_indices);
356
357 if (uv_scope == ABC_UV_SCOPE_NONE) {
358 return;
359 }
360
361 abc_data.uv_scope = uv_scope;
362 abc_data.uvs = uvsamp.getVals();
363 abc_data.uvs_indices = uvs_indices;
364
365 std::string name = Alembic::Abc::GetSourceName(uv.getMetaData());
366
367 /* According to the convention, primary UVs should have had their name
368 * set using Alembic::Abc::SetSourceName, but you can't expect everyone
369 * to follow it! :) */
370 if (name.empty()) {
371 name = uv.getName();
372 }
373
374 void *cd_ptr = config.add_customdata_cb(config.mesh, name.c_str(), CD_PROP_FLOAT2);
375 config.mloopuv = static_cast<float2 *>(cd_ptr);
376}
377
378static void *add_customdata_cb(Mesh *mesh, const char *name, int data_type)
379{
380 eCustomDataType cd_data_type = static_cast<eCustomDataType>(data_type);
381
382 /* unsupported custom data type -- don't do anything. */
383 if (!ELEM(cd_data_type, CD_PROP_FLOAT2, CD_PROP_BYTE_COLOR)) {
384 return nullptr;
385 }
386
388 &mesh->corner_data, cd_data_type, name, mesh->corners_num);
389 if (cd_ptr != nullptr) {
390 /* layer already exists, so just return it. */
391 return cd_ptr;
392 }
393
394 /* Create a new layer. */
395 int numloops = mesh->corners_num;
397 &mesh->corner_data, cd_data_type, CD_SET_DEFAULT, numloops, name);
398 return cd_ptr;
399}
400
401template<typename SampleType>
402static bool samples_have_same_topology(const SampleType &sample, const SampleType &ceil_sample)
403{
404 const P3fArraySamplePtr &positions = sample.getPositions();
405 const Alembic::Abc::Int32ArraySamplePtr &face_indices = sample.getFaceIndices();
406 const Alembic::Abc::Int32ArraySamplePtr &face_counts = sample.getFaceCounts();
407
408 const P3fArraySamplePtr &ceil_positions = ceil_sample.getPositions();
409 const Alembic::Abc::Int32ArraySamplePtr &ceil_face_indices = ceil_sample.getFaceIndices();
410 const Alembic::Abc::Int32ArraySamplePtr &ceil_face_counts = ceil_sample.getFaceCounts();
411
412 /* It the counters are different, we can be sure the topology is different. */
413 const bool different_counters = positions->size() != ceil_positions->size() ||
414 face_counts->size() != ceil_face_counts->size() ||
415 face_indices->size() != ceil_face_indices->size();
416 if (different_counters) {
417 return false;
418 }
419
420 /* Otherwise, we need to check the connectivity as files from e.g. videogrammetry may have the
421 * same face count, but different connections between faces. */
422
423 if (memcmp(face_counts->get(), ceil_face_counts->get(), face_counts->size() * sizeof(int))) {
424 return false;
425 }
426
427 if (memcmp(face_indices->get(), ceil_face_indices->get(), face_indices->size() * sizeof(int))) {
428 return false;
429 }
430
431 return true;
432}
433
434static void read_mesh_sample(const std::string &iobject_full_name,
435 ImportSettings *settings,
436 const IPolyMeshSchema &schema,
437 const ISampleSelector &selector,
438 CDStreamConfig &config)
439{
440 const IPolyMeshSchema::Sample sample = schema.getValue(selector);
441
442 AbcMeshData abc_mesh_data;
443 abc_mesh_data.face_counts = sample.getFaceCounts();
444 abc_mesh_data.face_indices = sample.getFaceIndices();
445 abc_mesh_data.positions = sample.getPositions();
446
447 const std::optional<SampleInterpolationSettings> interpolation_settings =
449 selector, schema.getTimeSampling(), schema.getNumSamples());
450
451 const bool use_vertex_interpolation = settings->read_flag & MOD_MESHSEQ_INTERPOLATE_VERTICES;
452 if (use_vertex_interpolation && interpolation_settings.has_value()) {
453 Alembic::AbcGeom::IPolyMeshSchema::Sample ceil_sample;
454 schema.get(ceil_sample, Alembic::Abc::ISampleSelector(interpolation_settings->ceil_index));
455 if (samples_have_same_topology(sample, ceil_sample)) {
456 /* Only set interpolation data if the samples are compatible. */
457 abc_mesh_data.ceil_positions = ceil_sample.getPositions();
458 abc_mesh_data.interpolation_settings = interpolation_settings;
459 }
460 }
461
462 if ((settings->read_flag & MOD_MESHSEQ_READ_UV) != 0) {
463 read_uvs_params(config, abc_mesh_data, schema.getUVsParam(), selector);
464 }
465
466 if ((settings->read_flag & MOD_MESHSEQ_READ_VERT) != 0) {
467 read_mverts(config, abc_mesh_data);
468 read_generated_coordinates(schema.getArbGeomParams(), config, selector);
469 }
470
471 if ((settings->read_flag & MOD_MESHSEQ_READ_POLY) != 0) {
472 read_mpolys(config, abc_mesh_data);
473 process_normals(config, schema.getNormalsParam(), selector);
474 }
475
476 if ((settings->read_flag & (MOD_MESHSEQ_READ_UV | MOD_MESHSEQ_READ_COLOR)) != 0) {
477 read_custom_data(iobject_full_name, schema.getArbGeomParams(), config, selector);
478 }
479
480 if (!settings->velocity_name.empty() && settings->velocity_scale != 0.0f) {
481 V3fArraySamplePtr velocities = get_velocity_prop(schema, selector, settings->velocity_name);
482 if (velocities) {
483 read_velocity(velocities, config, settings->velocity_scale);
484 }
485 }
486}
487
489{
490 CDStreamConfig config;
491 config.mesh = mesh;
492 config.positions = mesh->vert_positions_for_write().data();
493 config.corner_verts = mesh->corner_verts_for_write().data();
494 config.face_offsets = mesh->face_offsets_for_write().data();
495 config.totvert = mesh->verts_num;
496 config.totloop = mesh->corners_num;
497 config.faces_num = mesh->faces_num;
498 config.loopdata = &mesh->corner_data;
500
501 return config;
502}
503
504/* ************************************************************************** */
505
506AbcMeshReader::AbcMeshReader(const IObject &object, ImportSettings &settings)
507 : AbcObjectReader(object, settings)
508{
510
511 IPolyMesh ipoly_mesh(m_iobject, kWrapExisting);
512 m_schema = ipoly_mesh.getSchema();
513
515}
516
518{
519 return m_schema.valid();
520}
521
522template<class typedGeomParam>
523bool is_valid_animated(const ICompoundProperty arbGeomParams, const PropertyHeader &prop_header)
524{
525 if (!typedGeomParam::matches(prop_header)) {
526 return false;
527 }
528
529 typedGeomParam geom_param(arbGeomParams, prop_header.getName());
530 return geom_param.valid() && !geom_param.isConstant();
531}
532
533static bool has_animated_geom_params(const ICompoundProperty arbGeomParams)
534{
535 if (!arbGeomParams.valid()) {
536 return false;
537 }
538
539 const int num_props = arbGeomParams.getNumProperties();
540 for (int i = 0; i < num_props; i++) {
541 const PropertyHeader &prop_header = arbGeomParams.getPropertyHeader(i);
542
543 /* These are interpreted as vertex colors later (see 'read_custom_data'). */
544 if (is_valid_animated<IC3fGeomParam>(arbGeomParams, prop_header)) {
545 return true;
546 }
547 if (is_valid_animated<IC4fGeomParam>(arbGeomParams, prop_header)) {
548 return true;
549 }
550 }
551
552 return false;
553}
554
555/* Specialization of #has_animations() as defined in abc_reader_object.h. */
556template<> bool has_animations(Alembic::AbcGeom::IPolyMeshSchema &schema, ImportSettings *settings)
557{
558 if (settings->is_sequence || !schema.isConstant()) {
559 return true;
560 }
561
562 IV2fGeomParam uvsParam = schema.getUVsParam();
563 if (uvsParam.valid() && !uvsParam.isConstant()) {
564 return true;
565 }
566
567 IN3fGeomParam normalsParam = schema.getNormalsParam();
568 if (normalsParam.valid() && !normalsParam.isConstant()) {
569 return true;
570 }
571
572 ICompoundProperty arbGeomParams = schema.getArbGeomParams();
573 if (has_animated_geom_params(arbGeomParams)) {
574 return true;
575 }
576
577 return false;
578}
579
580void AbcMeshReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel)
581{
582 Mesh *mesh = BKE_mesh_add(bmain, m_data_name.c_str());
583
585 m_object->data = mesh;
586
587 Mesh *read_mesh = this->read_mesh(mesh, sample_sel, MOD_MESHSEQ_READ_ALL, "", 0.0f, nullptr);
588 if (read_mesh != mesh) {
590 }
591
593 BKE_mesh_validate(mesh, false, false);
594 }
595
596 readFaceSetsSample(bmain, mesh, sample_sel);
597
600 }
601}
602
604 const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header,
605 const Object *const ob,
606 const char **r_err_str) const
607{
608 if (!Alembic::AbcGeom::IPolyMesh::matches(alembic_header)) {
609 *r_err_str = RPT_(
610 "Object type mismatch, Alembic object path pointed to PolyMesh when importing, but not "
611 "any more");
612 return false;
613 }
614
615 if (ob->type != OB_MESH) {
616 *r_err_str = RPT_("Object type mismatch, Alembic object path points to PolyMesh");
617 return false;
618 }
619
620 return true;
621}
622
623bool AbcMeshReader::topology_changed(const Mesh *existing_mesh, const ISampleSelector &sample_sel)
624{
625 IPolyMeshSchema::Sample sample;
626 try {
627 sample = m_schema.getValue(sample_sel);
628 }
629 catch (Alembic::Util::Exception &ex) {
630 printf("Alembic: error reading mesh sample for '%s/%s' at time %f: %s\n",
631 m_iobject.getFullName().c_str(),
632 m_schema.getName().c_str(),
633 sample_sel.getRequestedTime(),
634 ex.what());
635 /* A similar error in read_mesh() would just return existing_mesh. */
636 return false;
637 }
638
639 const P3fArraySamplePtr &positions = sample.getPositions();
640 const Alembic::Abc::Int32ArraySamplePtr &face_indices = sample.getFaceIndices();
641 const Alembic::Abc::Int32ArraySamplePtr &face_counts = sample.getFaceCounts();
642
643 /* It the counters are different, we can be sure the topology is different. */
644 const bool different_counters = positions->size() != existing_mesh->verts_num ||
645 face_counts->size() != existing_mesh->faces_num ||
646 face_indices->size() != existing_mesh->corners_num;
647 if (different_counters) {
648 return true;
649 }
650
651 /* Check first if we indeed have multiple samples, unless we read a file sequence in which case
652 * we need to do a full topology comparison. */
653 if (!m_is_reading_a_file_sequence && (m_schema.getFaceIndicesProperty().getNumSamples() == 1 &&
654 m_schema.getFaceCountsProperty().getNumSamples() == 1))
655 {
656 return false;
657 }
658
659 /* Otherwise, we need to check the connectivity as files from e.g. videogrammetry may have the
660 * same face count, but different connections between faces. */
661 uint abc_index = 0;
662
663 const int *mesh_corner_verts = existing_mesh->corner_verts().data();
664 const int *mesh_face_offsets = existing_mesh->face_offsets().data();
665
666 for (int i = 0; i < face_counts->size(); i++) {
667 if (mesh_face_offsets[i] != abc_index) {
668 return true;
669 }
670
671 const int abc_face_size = (*face_counts)[i];
672 /* NOTE: Alembic data is stored in the reverse order. */
673 uint rev_loop_index = abc_index + (abc_face_size - 1);
674 for (int f = 0; f < abc_face_size; f++, abc_index++, rev_loop_index--) {
675 const int mesh_vert = mesh_corner_verts[rev_loop_index];
676 const int abc_vert = (*face_indices)[abc_index];
677 if (mesh_vert != abc_vert) {
678 return true;
679 }
680 }
681 }
682
683 return false;
684}
685
687 const Alembic::Abc::ISampleSelector &sample_sel,
688 const int read_flag,
689 const char *velocity_name,
690 const float velocity_scale,
691 const char **r_err_str)
692{
693 Mesh *mesh = geometry_set.get_mesh_for_write();
694
695 if (mesh == nullptr) {
696 return;
697 }
698
699 Mesh *new_mesh = read_mesh(
700 mesh, sample_sel, read_flag, velocity_name, velocity_scale, r_err_str);
701
702 geometry_set.replace_mesh(new_mesh);
703}
704
706 const ISampleSelector &sample_sel,
707 const int read_flag,
708 const char *velocity_name,
709 const float velocity_scale,
710 const char **r_err_str)
711{
712 IPolyMeshSchema::Sample sample;
713 try {
714 sample = m_schema.getValue(sample_sel);
715 }
716 catch (Alembic::Util::Exception &ex) {
717 if (r_err_str != nullptr) {
718 *r_err_str = RPT_("Error reading mesh sample; more detail on the console");
719 }
720 printf("Alembic: error reading mesh sample for '%s/%s' at time %f: %s\n",
721 m_iobject.getFullName().c_str(),
722 m_schema.getName().c_str(),
723 sample_sel.getRequestedTime(),
724 ex.what());
725 return existing_mesh;
726 }
727
728 const P3fArraySamplePtr &positions = sample.getPositions();
729 const Alembic::Abc::Int32ArraySamplePtr &face_indices = sample.getFaceIndices();
730 const Alembic::Abc::Int32ArraySamplePtr &face_counts = sample.getFaceCounts();
731
732 /* Do some very minimal mesh validation. */
733 const int poly_count = face_counts->size();
734 const int loop_count = face_indices->size();
735 /* This is the same test as in poly_to_tri_count(). */
736 if (poly_count > 0 && loop_count < poly_count * 2) {
737 if (r_err_str != nullptr) {
738 *r_err_str = RPT_("Invalid mesh; more detail on the console");
739 }
740 printf("Alembic: invalid mesh sample for '%s/%s' at time %f, less than 2 loops per face\n",
741 m_iobject.getFullName().c_str(),
742 m_schema.getName().c_str(),
743 sample_sel.getRequestedTime());
744 return existing_mesh;
745 }
746
747 Mesh *new_mesh = nullptr;
748
749 /* Only read point data when streaming meshes, unless we need to create new ones. */
750 ImportSettings settings;
751 settings.read_flag |= read_flag;
752 settings.velocity_name = velocity_name;
753 settings.velocity_scale = velocity_scale;
754
755 if (topology_changed(existing_mesh, sample_sel)) {
757 existing_mesh, positions->size(), 0, face_counts->size(), face_indices->size());
758
759 settings.read_flag |= MOD_MESHSEQ_READ_ALL;
760 }
761 else {
762 /* If the face count changed (e.g. by triangulation), only read points.
763 * This prevents crash from #49813.
764 * TODO(kevin): perhaps find a better way to do this? */
765 if (face_counts->size() != existing_mesh->faces_num ||
766 face_indices->size() != existing_mesh->corners_num)
767 {
768 settings.read_flag = MOD_MESHSEQ_READ_VERT;
769
770 if (r_err_str) {
771 *r_err_str = RPT_(
772 "Topology has changed, perhaps by triangulating the mesh. Only vertices will be "
773 "read!");
774 }
775 }
776 }
777
778 Mesh *mesh_to_export = new_mesh ? new_mesh : existing_mesh;
779 CDStreamConfig config = get_config(mesh_to_export);
780 config.time = sample_sel.getRequestedTime();
781 config.modifier_error_message = r_err_str;
782
783 read_mesh_sample(m_iobject.getFullName(), &settings, m_schema, sample_sel, config);
784
785 if (new_mesh) {
786 /* Here we assume that the number of materials doesn't change, i.e. that
787 * the material slots that were created when the object was loaded from
788 * Alembic are still valid now. */
789 size_t num_faces = new_mesh->faces_num;
790 if (num_faces > 0) {
791 std::map<std::string, int> mat_map;
792 bke::MutableAttributeAccessor attributes = new_mesh->attributes_for_write();
793 bke::SpanAttributeWriter<int> material_indices =
794 attributes.lookup_or_add_for_write_span<int>("material_index", bke::AttrDomain::Face);
795 assign_facesets_to_material_indices(sample_sel, material_indices.span, mat_map);
796 material_indices.finish();
797 }
798
799 return new_mesh;
800 }
801
802 return existing_mesh;
803}
804
805void AbcMeshReader::assign_facesets_to_material_indices(const ISampleSelector &sample_sel,
806 MutableSpan<int> material_indices,
807 std::map<std::string, int> &r_mat_map)
808{
809 std::vector<std::string> face_sets;
810 m_schema.getFaceSetNames(face_sets);
811
812 if (face_sets.empty()) {
813 return;
814 }
815
816 int current_mat = 0;
817
818 for (const std::string &grp_name : face_sets) {
819 if (r_mat_map.find(grp_name) == r_mat_map.end()) {
820 r_mat_map[grp_name] = ++current_mat;
821 }
822
823 const int assigned_mat = r_mat_map[grp_name];
824
825 const IFaceSet faceset = m_schema.getFaceSet(grp_name);
826
827 if (!faceset.valid()) {
828 std::cerr << " Face set " << grp_name << " invalid for " << m_object_name << "\n";
829 continue;
830 }
831
832 const IFaceSetSchema face_schem = faceset.getSchema();
833 const IFaceSetSchema::Sample face_sample = face_schem.getValue(sample_sel);
834 const Int32ArraySamplePtr group_faces = face_sample.getFaces();
835 const size_t num_group_faces = group_faces->size();
836
837 for (size_t l = 0; l < num_group_faces; l++) {
838 size_t pos = (*group_faces)[l];
839
840 if (pos >= material_indices.size()) {
841 std::cerr << "Faceset overflow on " << faceset.getName() << '\n';
842 break;
843 }
844
845 material_indices[pos] = assigned_mat - 1;
846 }
847 }
848}
849
850void AbcMeshReader::readFaceSetsSample(Main *bmain, Mesh *mesh, const ISampleSelector &sample_sel)
851{
852 std::map<std::string, int> mat_map;
853 bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
854 bke::SpanAttributeWriter<int> material_indices = attributes.lookup_or_add_for_write_span<int>(
855 "material_index", bke::AttrDomain::Face);
856 assign_facesets_to_material_indices(sample_sel, material_indices.span, mat_map);
857 material_indices.finish();
858 utils::assign_materials(bmain, m_object, mat_map);
859}
860
861/* ************************************************************************** */
862
863static void read_subd_sample(const std::string &iobject_full_name,
864 ImportSettings *settings,
865 const ISubDSchema &schema,
866 const ISampleSelector &selector,
867 CDStreamConfig &config)
868{
869 const ISubDSchema::Sample sample = schema.getValue(selector);
870
871 AbcMeshData abc_mesh_data;
872 abc_mesh_data.face_counts = sample.getFaceCounts();
873 abc_mesh_data.face_indices = sample.getFaceIndices();
874 abc_mesh_data.positions = sample.getPositions();
875
876 const std::optional<SampleInterpolationSettings> interpolation_settings =
878 selector, schema.getTimeSampling(), schema.getNumSamples());
879
880 const bool use_vertex_interpolation = settings->read_flag & MOD_MESHSEQ_INTERPOLATE_VERTICES;
881 if (use_vertex_interpolation && interpolation_settings.has_value()) {
882 Alembic::AbcGeom::ISubDSchema::Sample ceil_sample;
883 schema.get(ceil_sample, Alembic::Abc::ISampleSelector(interpolation_settings->ceil_index));
884 if (samples_have_same_topology(sample, ceil_sample)) {
885 /* Only set interpolation data if the samples are compatible. */
886 abc_mesh_data.ceil_positions = ceil_sample.getPositions();
887 abc_mesh_data.interpolation_settings = interpolation_settings;
888 }
889 }
890
891 if ((settings->read_flag & MOD_MESHSEQ_READ_UV) != 0) {
892 read_uvs_params(config, abc_mesh_data, schema.getUVsParam(), selector);
893 }
894
895 if ((settings->read_flag & MOD_MESHSEQ_READ_VERT) != 0) {
896 read_mverts(config, abc_mesh_data);
897 }
898
899 if ((settings->read_flag & MOD_MESHSEQ_READ_POLY) != 0) {
900 /* Alembic's 'SubD' scheme is used to store subdivision surfaces, i.e. the pre-subdivision
901 * mesh. Currently we don't add a subdivision modifier when we load such data. This code is
902 * assuming that the subdivided surface should be smooth. */
903 read_mpolys(config, abc_mesh_data);
904 process_no_normals(config);
905 }
906
907 if ((settings->read_flag & (MOD_MESHSEQ_READ_UV | MOD_MESHSEQ_READ_COLOR)) != 0) {
908 read_custom_data(iobject_full_name, schema.getArbGeomParams(), config, selector);
909 }
910
911 if (!settings->velocity_name.empty() && settings->velocity_scale != 0.0f) {
912 V3fArraySamplePtr velocities = get_velocity_prop(schema, selector, settings->velocity_name);
913 if (velocities) {
914 read_velocity(velocities, config, settings->velocity_scale);
915 }
916 }
917}
918
919static void read_vertex_creases(Mesh *mesh,
920 const Int32ArraySamplePtr &indices,
921 const FloatArraySamplePtr &sharpnesses)
922{
923 if (!(indices && sharpnesses && indices->size() == sharpnesses->size() && indices->size() != 0))
924 {
925 return;
926 }
927
928 float *vertex_crease_data = (float *)CustomData_add_layer_named(
929 &mesh->vert_data, CD_PROP_FLOAT, CD_SET_DEFAULT, mesh->verts_num, "crease_vert");
930 const int totvert = mesh->verts_num;
931
932 for (int i = 0, v = indices->size(); i < v; ++i) {
933 const int idx = (*indices)[i];
934
935 if (idx >= totvert) {
936 continue;
937 }
938
939 vertex_crease_data[idx] = (*sharpnesses)[i];
940 }
941}
942
943static void read_edge_creases(Mesh *mesh,
944 const Int32ArraySamplePtr &indices,
945 const FloatArraySamplePtr &sharpnesses)
946{
947 if (!(indices && sharpnesses)) {
948 return;
949 }
950
951 MutableSpan<int2> edges = mesh->edges_for_write();
952 Map<OrderedEdge, int> edge_hash;
953 edge_hash.reserve(edges.size());
954
955 float *creases = static_cast<float *>(CustomData_add_layer_named(
956 &mesh->edge_data, CD_PROP_FLOAT, CD_SET_DEFAULT, edges.size(), "crease_edge"));
957
958 for (const int i : edges.index_range()) {
959 edge_hash.add(edges[i], i);
960 }
961
962 for (int i = 0, s = 0, e = indices->size(); i < e; i += 2, s++) {
963 int v1 = (*indices)[i];
964 int v2 = (*indices)[i + 1];
965 const int *index = edge_hash.lookup_ptr({v1, v2});
966 if (!index) {
967 continue;
968 }
969
970 creases[*index] = unit_float_to_uchar_clamp((*sharpnesses)[s]);
971 }
972}
973
974/* ************************************************************************** */
975
976AbcSubDReader::AbcSubDReader(const IObject &object, ImportSettings &settings)
977 : AbcObjectReader(object, settings)
978{
980
981 ISubD isubd_mesh(m_iobject, kWrapExisting);
982 m_schema = isubd_mesh.getSchema();
983
985}
986
988{
989 return m_schema.valid();
990}
991
993 const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header,
994 const Object *const ob,
995 const char **r_err_str) const
996{
997 if (!Alembic::AbcGeom::ISubD::matches(alembic_header)) {
998 *r_err_str = RPT_(
999 "Object type mismatch, Alembic object path pointed to SubD when importing, but not any "
1000 "more");
1001 return false;
1002 }
1003
1004 if (ob->type != OB_MESH) {
1005 *r_err_str = RPT_("Object type mismatch, Alembic object path points to SubD");
1006 return false;
1007 }
1008
1009 return true;
1010}
1011
1012void AbcSubDReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel)
1013{
1014 Mesh *mesh = BKE_mesh_add(bmain, m_data_name.c_str());
1015
1017 m_object->data = mesh;
1018
1019 Mesh *read_mesh = this->read_mesh(mesh, sample_sel, MOD_MESHSEQ_READ_ALL, "", 0.0f, nullptr);
1020 if (read_mesh != mesh) {
1021 BKE_mesh_nomain_to_mesh(read_mesh, mesh, m_object);
1022 }
1023
1024 ISubDSchema::Sample sample;
1025 try {
1026 sample = m_schema.getValue(sample_sel);
1027 }
1028 catch (Alembic::Util::Exception &ex) {
1029 printf("Alembic: error reading mesh sample for '%s/%s' at time %f: %s\n",
1030 m_iobject.getFullName().c_str(),
1031 m_schema.getName().c_str(),
1032 sample_sel.getRequestedTime(),
1033 ex.what());
1034 return;
1035 }
1036
1037 read_edge_creases(mesh, sample.getCreaseIndices(), sample.getCreaseSharpnesses());
1038
1039 read_vertex_creases(mesh, sample.getCornerIndices(), sample.getCornerSharpnesses());
1040
1042 BKE_mesh_validate(mesh, false, false);
1043 }
1044
1047 }
1048}
1049
1050Mesh *AbcSubDReader::read_mesh(Mesh *existing_mesh,
1051 const ISampleSelector &sample_sel,
1052 const int read_flag,
1053 const char *velocity_name,
1054 const float velocity_scale,
1055 const char **r_err_str)
1056{
1057 ISubDSchema::Sample sample;
1058 try {
1059 sample = m_schema.getValue(sample_sel);
1060 }
1061 catch (Alembic::Util::Exception &ex) {
1062 if (r_err_str != nullptr) {
1063 *r_err_str = RPT_("Error reading mesh sample; more detail on the console");
1064 }
1065 printf("Alembic: error reading mesh sample for '%s/%s' at time %f: %s\n",
1066 m_iobject.getFullName().c_str(),
1067 m_schema.getName().c_str(),
1068 sample_sel.getRequestedTime(),
1069 ex.what());
1070 return existing_mesh;
1071 }
1072
1073 const P3fArraySamplePtr &positions = sample.getPositions();
1074 const Alembic::Abc::Int32ArraySamplePtr &face_indices = sample.getFaceIndices();
1075 const Alembic::Abc::Int32ArraySamplePtr &face_counts = sample.getFaceCounts();
1076
1077 Mesh *new_mesh = nullptr;
1078
1079 ImportSettings settings;
1080 settings.read_flag |= read_flag;
1081 settings.velocity_name = velocity_name;
1082 settings.velocity_scale = velocity_scale;
1083
1084 if (existing_mesh->verts_num != positions->size()) {
1086 existing_mesh, positions->size(), 0, face_counts->size(), face_indices->size());
1087
1088 settings.read_flag |= MOD_MESHSEQ_READ_ALL;
1089 }
1090 else {
1091 /* If the face count changed (e.g. by triangulation), only read points.
1092 * This prevents crash from #49813.
1093 * TODO(kevin): perhaps find a better way to do this? */
1094 if (face_counts->size() != existing_mesh->faces_num ||
1095 face_indices->size() != existing_mesh->corners_num)
1096 {
1097 settings.read_flag = MOD_MESHSEQ_READ_VERT;
1098
1099 if (r_err_str) {
1100 *r_err_str = RPT_(
1101 "Topology has changed, perhaps by triangulating the mesh. Only vertices will be "
1102 "read!");
1103 }
1104 }
1105 }
1106
1107 /* Only read point data when streaming meshes, unless we need to create new ones. */
1108 Mesh *mesh_to_export = new_mesh ? new_mesh : existing_mesh;
1109 CDStreamConfig config = get_config(mesh_to_export);
1110 config.time = sample_sel.getRequestedTime();
1111 config.modifier_error_message = r_err_str;
1112 read_subd_sample(m_iobject.getFullName(), &settings, m_schema, sample_sel, config);
1113
1114 return mesh_to_export;
1115}
1116
1118 const Alembic::Abc::ISampleSelector &sample_sel,
1119 const int read_flag,
1120 const char *velocity_name,
1121 const float velocity_scale,
1122 const char **r_err_str)
1123{
1124 Mesh *mesh = geometry_set.get_mesh_for_write();
1125
1126 if (mesh == nullptr) {
1127 return;
1128 }
1129
1130 Mesh *new_mesh = read_mesh(
1131 mesh, sample_sel, read_flag, velocity_name, velocity_scale, r_err_str);
1132
1133 geometry_set.replace_mesh(new_mesh);
1134}
1135
1136} // namespace blender::io::alembic
CustomData interface, see also DNA_customdata_types.h.
void * CustomData_get_layer_named_for_write(CustomData *data, eCustomDataType type, blender::StringRef name, int totelem)
@ CD_SET_DEFAULT
void * CustomData_add_layer_named(CustomData *data, eCustomDataType type, eCDAllocType alloctype, int totelem, blender::StringRef name)
void id_us_min(ID *id)
Definition lib_id.cc:359
General operations, lookup, etc. for materials.
void BKE_object_material_assign_single_obdata(struct Main *bmain, struct Object *ob, struct Material *ma, short act)
struct Material * BKE_material_add(struct Main *bmain, const char *name)
void BKE_mesh_set_custom_normals(Mesh *mesh, float(*r_custom_loop_normals)[3])
Mesh * BKE_mesh_new_nomain_from_template(const Mesh *me_src, int verts_num, int edges_num, int faces_num, int corners_num)
bool BKE_mesh_validate(Mesh *mesh, bool do_verbose, bool cddata_check_mask)
void BKE_mesh_nomain_to_mesh(Mesh *mesh_src, Mesh *mesh_dst, Object *ob)
Mesh * BKE_mesh_add(Main *bmain, const char *name)
void BKE_mesh_set_custom_normals_from_verts(Mesh *mesh, float(*r_custom_vert_normals)[3])
General operations, lookup, etc. for blender objects.
Object * BKE_object_add_only_object(Main *bmain, int type, const char *name) ATTR_RETURNS_NONNULL
#define BLI_assert(a)
Definition BLI_assert.h:50
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:57
#define BLI_INLINE
#define LISTBASE_FOREACH(type, var, list)
void interp_v3_v3v3(float r[3], const float a[3], const float b[3], float t)
Definition math_vector.c:36
unsigned int uint
#define ELEM(...)
#define RPT_(msgid)
@ CD_PROP_BYTE_COLOR
@ CD_PROP_FLOAT
@ CD_PROP_FLOAT2
#define MAXMAT
@ MOD_MESHSEQ_READ_COLOR
@ MOD_MESHSEQ_READ_VERT
@ MOD_MESHSEQ_INTERPOLATE_VERTICES
@ MOD_MESHSEQ_READ_UV
@ MOD_MESHSEQ_READ_POLY
#define MOD_MESHSEQ_READ_ALL
Object is a sort of wrapper for general info.
@ OB_MESH
static NewVert * mesh_vert(VMesh *vm, int i, int j, int k)
ATTR_WARN_UNUSED_RESULT const BMVert * v2
ATTR_WARN_UNUSED_RESULT const BMLoop * l
ATTR_WARN_UNUSED_RESULT const BMVert const BMEdge * e
ATTR_WARN_UNUSED_RESULT const BMVert * v
const Value * lookup_ptr(const Key &key) const
Definition BLI_map.hh:484
bool add(const Key &key, const Value &value)
Definition BLI_map.hh:271
void reserve(int64_t n)
Definition BLI_map.hh:979
constexpr int64_t size() const
Definition BLI_span.hh:494
struct Mesh * read_mesh(struct Mesh *existing_mesh, const Alembic::Abc::ISampleSelector &sample_sel, int read_flag, const char *velocity_name, float velocity_scale, const char **r_err_str)
void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override
bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, const Object *const ob, const char **r_err_str) const override
void read_geometry(bke::GeometrySet &geometry_set, const Alembic::Abc::ISampleSelector &sample_sel, int read_flag, const char *velocity_name, float velocity_scale, const char **r_err_str) override
bool topology_changed(const Mesh *existing_mesh, const Alembic::Abc::ISampleSelector &sample_sel) override
AbcMeshReader(const Alembic::Abc::IObject &object, ImportSettings &settings)
bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, const Object *const ob, const char **r_err_str) const override
void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override
void read_geometry(bke::GeometrySet &geometry_set, const Alembic::Abc::ISampleSelector &sample_sel, int read_flag, const char *velocity_name, const float velocity_scale, const char **r_err_str) override
AbcSubDReader(const Alembic::Abc::IObject &object, ImportSettings &settings)
#define printf
Material material
draw_view in_light_buf[] float
void *(* MEM_malloc_arrayN)(size_t len, size_t size, const char *str)
Definition mallocn.cc:45
void MEM_freeN(void *vmemh)
Definition mallocn.cc:105
#define unit_float_to_uchar_clamp(val)
void mesh_calc_edges(Mesh &mesh, bool keep_existing_edges, bool select_new_edges)
void mesh_vert_normals_assign(Mesh &mesh, Span< float3 > vert_normals)
static void assign_materials(Main *bmain, Object *ob, const std::map< std::string, int > &mat_index_map)
static std::map< std::string, Material * > build_material_map(const Main *bmain)
AbcUvScope get_uv_scope(const Alembic::AbcGeom::GeometryScope scope, const CDStreamConfig &config, const Alembic::AbcGeom::UInt32ArraySamplePtr &indices)
static void process_normals(CDStreamConfig &config, const IN3fGeomParam &normals, const ISampleSelector &selector)
void read_velocity(const V3fArraySamplePtr &velocities, const CDStreamConfig &config, const float velocity_scale)
void get_min_max_time(const Alembic::AbcGeom::IObject &object, const Schema &schema, chrono_t &min, chrono_t &max)
Definition abc_util.h:82
static void read_mesh_sample(const std::string &iobject_full_name, ImportSettings *settings, const IPolyMeshSchema &schema, const ISampleSelector &selector, CDStreamConfig &config)
BLI_INLINE void read_uvs_params(CDStreamConfig &config, AbcMeshData &abc_data, const IV2fGeomParam &uv, const ISampleSelector &selector)
static void read_mverts_interp(float3 *vert_positions, const P3fArraySamplePtr &positions, const P3fArraySamplePtr &ceil_positions, const double weight)
static void process_loop_normals(CDStreamConfig &config, const N3fArraySamplePtr loop_normals_ptr)
bool is_valid_animated(const ICompoundProperty arbGeomParams, const PropertyHeader &prop_header)
static CDStreamConfig get_config(Mesh *mesh)
static void read_vertex_creases(Mesh *mesh, const Int32ArraySamplePtr &indices, const FloatArraySamplePtr &sharpnesses)
static bool samples_have_same_topology(const SampleType &sample, const SampleType &ceil_sample)
BLI_INLINE void copy_zup_from_yup(float zup[3], const float yup[3])
static void process_vertex_normals(CDStreamConfig &config, const N3fArraySamplePtr vertex_normals_ptr)
void read_custom_data(const std::string &iobject_full_name, const ICompoundProperty &prop, const CDStreamConfig &config, const Alembic::Abc::ISampleSelector &iss)
static void read_mpolys(CDStreamConfig &config, const AbcMeshData &mesh_data)
static void read_edge_creases(Mesh *mesh, const Int32ArraySamplePtr &indices, const FloatArraySamplePtr &sharpnesses)
static void read_subd_sample(const std::string &iobject_full_name, ImportSettings *settings, const ISubDSchema &schema, const ISampleSelector &selector, CDStreamConfig &config)
static bool has_animated_geom_params(const ICompoundProperty arbGeomParams)
V3fArraySamplePtr get_velocity_prop(const Alembic::Abc::ICompoundProperty &schema, const Alembic::AbcGeom::ISampleSelector &selector, const std::string &name)
Definition abc_util.cc:119
static void process_no_normals(CDStreamConfig &)
void read_generated_coordinates(const ICompoundProperty &prop, const CDStreamConfig &config, const Alembic::Abc::ISampleSelector &iss)
static void read_mverts(CDStreamConfig &config, const AbcMeshData &mesh_data)
bool has_animations(Alembic::AbcGeom::IPolyMeshSchema &schema, ImportSettings *settings)
std::optional< SampleInterpolationSettings > get_sample_interpolation_settings(const Alembic::AbcGeom::ISampleSelector &selector, const Alembic::AbcCoreAbstract::TimeSamplingPtr &time_sampling, size_t samples_number)
Definition abc_util.cc:157
static void * add_customdata_cb(Mesh *mesh, const char *name, int data_type)
__int64 int64_t
Definition stdint.h:89
ListBase materials
Definition BKE_main.hh:216
int corners_num
int faces_num
int verts_num
void replace_mesh(Mesh *mesh, GeometryOwnershipType ownership=GeometryOwnershipType::Owned)
std::optional< SampleInterpolationSettings > interpolation_settings
void *(* add_customdata_cb)(Mesh *mesh, const char *name, int data_type)