Blender V5.0
abc_writer_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
8
9#include "abc_writer_mesh.h"
12
13#include "BKE_attribute.hh"
14#include "BKE_lib_id.hh"
15#include "BKE_material.hh"
16#include "BKE_mesh.hh"
17#include "BKE_mesh_wrapper.hh"
18#include "BKE_object.hh"
19#include "BKE_subdiv.hh"
20
21#include "bmesh.hh"
22#include "bmesh_tools.hh"
23
25#include "DNA_material_types.h"
26#include "DNA_mesh_types.h"
27#include "DNA_modifier_types.h"
28#include "DNA_object_types.h"
29
30#include "CLG_log.h"
31static CLG_LogRef LOG = {"io.alembic"};
32
33using Alembic::Abc::FloatArraySample;
34using Alembic::Abc::Int32ArraySample;
35using Alembic::Abc::OObject;
36using Alembic::Abc::V2fArraySample;
37using Alembic::Abc::V3fArraySample;
38
39using Alembic::AbcGeom::kFacevaryingScope;
40using Alembic::AbcGeom::OBoolProperty;
41using Alembic::AbcGeom::OCompoundProperty;
42using Alembic::AbcGeom::OFaceSet;
43using Alembic::AbcGeom::OFaceSetSchema;
44using Alembic::AbcGeom::ON3fGeomParam;
45using Alembic::AbcGeom::OPolyMesh;
46using Alembic::AbcGeom::OPolyMeshSchema;
47using Alembic::AbcGeom::OSubD;
48using Alembic::AbcGeom::OSubDSchema;
49using Alembic::AbcGeom::OV2fGeomParam;
50using Alembic::AbcGeom::UInt32ArraySample;
51
52namespace blender::io::alembic {
53
54/* NOTE: Alembic's polygon winding order is clockwise, to match with Renderman. */
55
56static void get_vertices(Mesh *mesh, std::vector<Imath::V3f> &points);
57static void get_topology(Mesh *mesh,
58 std::vector<int32_t> &face_verts,
59 std::vector<int32_t> &loop_counts);
60static void get_edge_creases(Mesh *mesh,
61 std::vector<int32_t> &indices,
62 std::vector<int32_t> &lengths,
63 std::vector<float> &sharpnesses);
64static void get_vert_creases(Mesh *mesh,
65 std::vector<int32_t> &indices,
66 std::vector<float> &sharpnesses);
67static void get_loop_normals(const Mesh *mesh, std::vector<Imath::V3f> &normals);
68
73
75{
76 if (!args_.export_params->apply_subdiv && export_as_subdivision_surface(context->object)) {
77 is_subd_ = args_.export_params->use_subdiv_schema;
78 }
79
80 if (is_subd_) {
81 CLOG_DEBUG(&LOG, "exporting OSubD %s", args_.abc_path.c_str());
82 abc_subdiv_ = OSubD(args_.abc_parent, args_.abc_name, timesample_index_);
83 abc_subdiv_schema_ = abc_subdiv_.getSchema();
84 }
85 else {
86 CLOG_DEBUG(&LOG, "exporting OPolyMesh %s", args_.abc_path.c_str());
87 abc_poly_mesh_ = OPolyMesh(args_.abc_parent, args_.abc_name, timesample_index_);
88 abc_poly_mesh_schema_ = abc_poly_mesh_.getSchema();
89
90 OCompoundProperty typeContainer = abc_poly_mesh_.getSchema().getUserProperties();
91 OBoolProperty type(typeContainer, "meshtype");
92 type.set(subsurf_modifier_ == nullptr);
93 }
94}
95
96Alembic::Abc::OObject ABCGenericMeshWriter::get_alembic_object() const
97{
98 if (is_subd_) {
99 return abc_subdiv_;
100 }
101 return abc_poly_mesh_;
102}
103
104Alembic::Abc::OCompoundProperty ABCGenericMeshWriter::abc_prop_for_custom_props()
105{
106 if (is_subd_) {
107 return abc_schema_prop_for_custom_props(abc_subdiv_schema_);
108 }
109 return abc_schema_prop_for_custom_props(abc_poly_mesh_schema_);
110}
111
113{
114 ModifierData *md = static_cast<ModifierData *>(ob_eval->modifiers.last);
115
116 for (; md; md = md->prev) {
117 /* This modifier has been temporarily disabled by SubdivModifierDisabler,
118 * so this indicates this is to be exported as subdivision surface. */
120 return true;
121 }
122 }
123
124 return false;
125}
126
128{
129 return context->is_object_visible(args_.export_params->evaluation_mode);
130}
131
133{
134 Object *object = context.object;
135 bool needsfree = false;
136
137 Mesh *mesh = get_export_mesh(object, needsfree);
138
139 if (mesh == nullptr) {
140 return;
141 }
142
143 /* Ensure data exists if currently in edit mode. */
145
146 if (args_.export_params->triangulate) {
147 const bool tag_only = false;
148 const int quad_method = args_.export_params->quad_method;
149 const int ngon_method = args_.export_params->ngon_method;
150
151 BMeshCreateParams bmesh_create_params{};
152 BMeshFromMeshParams bmesh_from_mesh_params{};
153 bmesh_from_mesh_params.calc_face_normal = true;
154 bmesh_from_mesh_params.calc_vert_normal = true;
155 BMesh *bm = BKE_mesh_to_bmesh_ex(mesh, &bmesh_create_params, &bmesh_from_mesh_params);
156
157 BM_mesh_triangulate(bm, quad_method, ngon_method, 4, tag_only, nullptr, nullptr, nullptr);
158
159 Mesh *triangulated_mesh = BKE_mesh_from_bmesh_for_eval_nomain(bm, nullptr, mesh);
161
162 if (needsfree) {
163 free_export_mesh(mesh);
164 }
165 mesh = triangulated_mesh;
166 needsfree = true;
167 }
168
169 m_custom_data_config.pack_uvs = args_.export_params->packuv;
170 m_custom_data_config.mesh = mesh;
171 m_custom_data_config.face_offsets = mesh->face_offsets_for_write().data();
172 m_custom_data_config.corner_verts = mesh->corner_verts_for_write().data();
173 m_custom_data_config.faces_num = mesh->faces_num;
174 m_custom_data_config.totloop = mesh->corners_num;
175 m_custom_data_config.totvert = mesh->verts_num;
176 m_custom_data_config.timesample_index = timesample_index_;
177
178 try {
179 if (is_subd_) {
180 write_subd(context, mesh);
181 }
182 else {
183 write_mesh(context, mesh);
184 }
185
186 if (needsfree) {
187 free_export_mesh(mesh);
188 }
189 }
190 catch (...) {
191 if (needsfree) {
192 free_export_mesh(mesh);
193 }
194 throw;
195 }
196}
197
199{
200 BKE_id_free(nullptr, mesh);
201}
202
203void ABCGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh)
204{
205 std::vector<Imath::V3f> points, normals;
206 std::vector<int32_t> face_verts, loop_counts;
207 std::vector<Imath::V3f> velocities;
208
209 get_vertices(mesh, points);
210 get_topology(mesh, face_verts, loop_counts);
211
213 write_face_sets(context.object, mesh, abc_poly_mesh_schema_);
214 }
215
216 OPolyMeshSchema::Sample mesh_sample = OPolyMeshSchema::Sample(
217 V3fArraySample(points), Int32ArraySample(face_verts), Int32ArraySample(loop_counts));
218
219 UVSample uvs_and_indices;
220
221 if (args_.export_params->uvs) {
222 const char *name = get_uv_sample(uvs_and_indices, m_custom_data_config, &mesh->corner_data);
223
224 if (!uvs_and_indices.indices.empty() && !uvs_and_indices.uvs.empty()) {
225 OV2fGeomParam::Sample uv_sample;
226 uv_sample.setVals(V2fArraySample(uvs_and_indices.uvs));
227 uv_sample.setIndices(UInt32ArraySample(uvs_and_indices.indices));
228 uv_sample.setScope(kFacevaryingScope);
229
230 abc_poly_mesh_schema_.setUVSourceName(name);
231 mesh_sample.setUVs(uv_sample);
232 }
233
234 write_custom_data(abc_poly_mesh_schema_.getArbGeomParams(),
235 m_custom_data_config,
236 &mesh->corner_data,
238 }
239
242
243 ON3fGeomParam::Sample normals_sample;
244 if (!normals.empty()) {
245 normals_sample.setScope(kFacevaryingScope);
246 normals_sample.setVals(V3fArraySample(normals));
247 }
248
249 mesh_sample.setNormals(normals_sample);
250 }
251
252 if (args_.export_params->orcos) {
253 write_generated_coordinates(abc_poly_mesh_schema_.getArbGeomParams(), m_custom_data_config);
254 }
255
256 if (get_velocities(mesh, velocities)) {
257 mesh_sample.setVelocities(V3fArraySample(velocities));
258 }
259
261 mesh_sample.setSelfBounds(bounding_box_);
262
263 abc_poly_mesh_schema_.set(mesh_sample);
264
265 write_arb_geo_params(mesh);
266}
267
268void ABCGenericMeshWriter::write_subd(HierarchyContext &context, Mesh *mesh)
269{
270 std::vector<float> edge_crease_sharpness, vert_crease_sharpness;
271 std::vector<Imath::V3f> points;
272 std::vector<int32_t> face_verts, loop_counts;
273 std::vector<int32_t> edge_crease_indices, edge_crease_lengths, vert_crease_indices;
274
275 get_vertices(mesh, points);
276 get_topology(mesh, face_verts, loop_counts);
277 get_edge_creases(mesh, edge_crease_indices, edge_crease_lengths, edge_crease_sharpness);
278 get_vert_creases(mesh, vert_crease_indices, vert_crease_sharpness);
279
280 if (!frame_has_been_written_ && args_.export_params->face_sets) {
281 write_face_sets(context.object, mesh, abc_subdiv_schema_);
282 }
283
284 OSubDSchema::Sample subdiv_sample = OSubDSchema::Sample(
285 V3fArraySample(points), Int32ArraySample(face_verts), Int32ArraySample(loop_counts));
286
287 UVSample sample;
288 if (args_.export_params->uvs) {
289 const char *name = get_uv_sample(sample, m_custom_data_config, &mesh->corner_data);
290
291 if (!sample.indices.empty() && !sample.uvs.empty()) {
292 OV2fGeomParam::Sample uv_sample;
293 uv_sample.setVals(V2fArraySample(sample.uvs));
294 uv_sample.setIndices(UInt32ArraySample(sample.indices));
295 uv_sample.setScope(kFacevaryingScope);
296
297 abc_subdiv_schema_.setUVSourceName(name);
298 subdiv_sample.setUVs(uv_sample);
299 }
300
301 write_custom_data(abc_subdiv_schema_.getArbGeomParams(),
302 m_custom_data_config,
303 &mesh->corner_data,
305 }
306
307 if (args_.export_params->orcos) {
308 write_generated_coordinates(abc_subdiv_schema_.getArbGeomParams(), m_custom_data_config);
309 }
310
311 if (!edge_crease_indices.empty()) {
312 subdiv_sample.setCreaseIndices(Int32ArraySample(edge_crease_indices));
313 subdiv_sample.setCreaseLengths(Int32ArraySample(edge_crease_lengths));
314 subdiv_sample.setCreaseSharpnesses(FloatArraySample(edge_crease_sharpness));
315 }
316
317 if (!vert_crease_indices.empty()) {
318 subdiv_sample.setCornerIndices(Int32ArraySample(vert_crease_indices));
319 subdiv_sample.setCornerSharpnesses(FloatArraySample(vert_crease_sharpness));
320 }
321
323 subdiv_sample.setSelfBounds(bounding_box_);
324 abc_subdiv_schema_.set(subdiv_sample);
325
326 write_arb_geo_params(mesh);
327}
328
329template<typename Schema>
330void ABCGenericMeshWriter::write_face_sets(Object *object, Mesh *mesh, Schema &schema)
331{
332 std::map<std::string, std::vector<int32_t>> geo_groups;
333 get_geo_groups(object, mesh, geo_groups);
334
335 std::map<std::string, std::vector<int32_t>>::iterator it;
336 for (it = geo_groups.begin(); it != geo_groups.end(); ++it) {
337 OFaceSet face_set = schema.createFaceSet(it->first);
338 OFaceSetSchema::Sample samp;
339 samp.setFaces(Int32ArraySample(it->second));
340 face_set.getSchema().set(samp);
341 }
342}
343
344void ABCGenericMeshWriter::write_arb_geo_params(Mesh *mesh)
345{
346 if (!args_.export_params->vcolors) {
347 return;
348 }
349
350 OCompoundProperty arb_geom_params;
351 if (is_subd_) {
352 arb_geom_params = abc_subdiv_.getSchema().getArbGeomParams();
353 }
354 else {
355 arb_geom_params = abc_poly_mesh_.getSchema().getArbGeomParams();
356 }
357 write_custom_data(arb_geom_params, m_custom_data_config, &mesh->corner_data, CD_PROP_BYTE_COLOR);
358}
359
360bool ABCGenericMeshWriter::get_velocities(Mesh *mesh, std::vector<Imath::V3f> &vels)
361{
362 /* Export velocity attribute output by fluid sim, sequence cache modifier
363 * and geometry nodes. */
364 AttributeOwner owner = AttributeOwner::from_id(&mesh->id);
365 const CustomDataLayer *velocity_layer = BKE_attribute_find(
366 owner, "velocity", CD_PROP_FLOAT3, bke::AttrDomain::Point);
367
368 if (velocity_layer == nullptr) {
369 return false;
370 }
371
372 const int totverts = mesh->verts_num;
373 const float (*mesh_velocities)[3] = reinterpret_cast<float (*)[3]>(velocity_layer->data);
374
375 vels.clear();
376 vels.resize(totverts);
377
378 for (int i = 0; i < totverts; i++) {
379 copy_yup_from_zup(vels[i].getValue(), mesh_velocities[i]);
380 }
381
382 return true;
383}
384
385void ABCGenericMeshWriter::get_geo_groups(Object *object,
386 Mesh *mesh,
387 std::map<std::string, std::vector<int32_t>> &geo_groups)
388{
389 const bke::AttributeAccessor attributes = mesh->attributes();
390 const VArraySpan<int> material_indices = *attributes.lookup_or_default<int>(
391 "material_index", bke::AttrDomain::Face, 0);
392
393 for (const int i : material_indices.index_range()) {
394 short mnr = material_indices[i];
395
396 Material *mat = BKE_object_material_get(object, mnr + 1);
397
398 if (!mat) {
399 continue;
400 }
401
402 std::string name = args_.hierarchy_iterator->get_id_name(&mat->id);
403
404 if (geo_groups.find(name) == geo_groups.end()) {
405 std::vector<int32_t> faceArray;
406 geo_groups[name] = faceArray;
407 }
408
409 geo_groups[name].push_back(i);
410 }
411
412 if (geo_groups.empty()) {
413 Material *mat = BKE_object_material_get(object, 1);
414
415 std::string name = (mat) ? args_.hierarchy_iterator->get_id_name(&mat->id) : "default";
416
417 std::vector<int32_t> faceArray;
418
419 for (int i = 0, e = mesh->totface_legacy; i < e; i++) {
420 faceArray.push_back(i);
421 }
422
423 geo_groups[name] = faceArray;
424 }
425}
426
427/* NOTE: Alembic's polygon winding order is clockwise, to match with Renderman. */
428
429static void get_vertices(Mesh *mesh, std::vector<Imath::V3f> &points)
430{
431 points.clear();
432 points.resize(mesh->verts_num);
433
434 const Span<float3> positions = mesh->vert_positions();
435 for (int i = 0, e = mesh->verts_num; i < e; i++) {
436 copy_yup_from_zup(points[i].getValue(), positions[i]);
437 }
438}
439
440static void get_topology(Mesh *mesh,
441 std::vector<int32_t> &face_verts,
442 std::vector<int32_t> &loop_counts)
443{
444 const OffsetIndices faces = mesh->faces();
445 const Span<int> corner_verts = mesh->corner_verts();
446
447 face_verts.clear();
448 loop_counts.clear();
449 face_verts.reserve(corner_verts.size());
450 loop_counts.reserve(faces.size());
451
452 /* NOTE: data needs to be written in the reverse order. */
453 for (const int i : faces.index_range()) {
454 const IndexRange face = faces[i];
455 loop_counts.push_back(face.size());
456
457 int corner = face.start() + (face.size() - 1);
458 for (int j = 0; j < face.size(); j++, corner--) {
459 face_verts.push_back(corner_verts[corner]);
460 }
461 }
462}
463
464static void get_edge_creases(Mesh *mesh,
465 std::vector<int32_t> &indices,
466 std::vector<int32_t> &lengths,
467 std::vector<float> &sharpnesses)
468{
469 indices.clear();
470 lengths.clear();
471 sharpnesses.clear();
472
473 const bke::AttributeAccessor attributes = mesh->attributes();
474 const bke::AttributeReader attribute = attributes.lookup<float>("crease_edge",
476 if (!attribute) {
477 return;
478 }
479 const VArraySpan creases(*attribute);
480 const Span<int2> edges = mesh->edges();
481 for (const int i : edges.index_range()) {
482 const float crease = std::clamp(creases[i], 0.0f, 1.0f);
483
484 if (crease != 0.0f) {
485 indices.push_back(edges[i][0]);
486 indices.push_back(edges[i][1]);
487 sharpnesses.push_back(bke::subdiv::crease_to_sharpness(crease));
488 }
489 }
490
491 lengths.resize(sharpnesses.size(), 2);
492}
493
494static void get_vert_creases(Mesh *mesh,
495 std::vector<int32_t> &indices,
496 std::vector<float> &sharpnesses)
497{
498 indices.clear();
499 sharpnesses.clear();
500
501 const bke::AttributeAccessor attributes = mesh->attributes();
502 const bke::AttributeReader attribute = attributes.lookup<float>("crease_vert",
504 if (!attribute) {
505 return;
506 }
507 const VArraySpan creases(*attribute);
508 for (const int i : creases.index_range()) {
509 const float crease = std::clamp(creases[i], 0.0f, 1.0f);
510
511 if (crease != 0.0f) {
512 indices.push_back(i);
513 sharpnesses.push_back(bke::subdiv::crease_to_sharpness(crease));
514 }
515 }
516}
517
518static void get_loop_normals(const Mesh *mesh, std::vector<Imath::V3f> &normals)
519{
520 normals.clear();
521
522 switch (mesh->normals_domain()) {
524 /* If all faces are smooth shaded, and there are no custom normals, we don't need to
525 * export normals at all. This is also done by other software, see #71246. */
526 break;
527 }
529 normals.resize(mesh->corners_num);
530 MutableSpan dst_normals(reinterpret_cast<float3 *>(normals.data()), normals.size());
531
532 const OffsetIndices faces = mesh->faces();
533 const Span<float3> face_normals = mesh->face_normals();
534 threading::parallel_for(faces.index_range(), 1024, [&](const IndexRange range) {
535 for (const int i : range) {
536 float3 y_up;
537 copy_yup_from_zup(y_up, face_normals[i]);
538 dst_normals.slice(faces[i]).fill(y_up);
539 }
540 });
541 break;
542 }
544 normals.resize(mesh->corners_num);
545 MutableSpan dst_normals(reinterpret_cast<float3 *>(normals.data()), normals.size());
546
547 /* NOTE: data needs to be written in the reverse order. */
548 const OffsetIndices faces = mesh->faces();
549 const Span<float3> corner_normals = mesh->corner_normals();
550 threading::parallel_for(faces.index_range(), 1024, [&](const IndexRange range) {
551 for (const int i : range) {
552 const IndexRange face = faces[i];
553 for (const int i : face.index_range()) {
554 copy_yup_from_zup(dst_normals[face.last(i)], corner_normals[face[i]]);
555 }
556 }
557 });
558 break;
559 }
560 }
561}
562
564
565Mesh *ABCMeshWriter::get_export_mesh(Object *object_eval, bool & /*r_needsfree*/)
566{
567 return BKE_object_get_evaluated_mesh(object_eval);
568}
569
570} // namespace blender::io::alembic
struct CustomDataLayer * BKE_attribute_find(const AttributeOwner &owner, blender::StringRef name, eCustomDataType type, blender::bke::AttrDomain domain)
Definition attribute.cc:614
void BKE_id_free(Main *bmain, void *idv)
General operations, lookup, etc. for materials.
Material * BKE_object_material_get(Object *ob, short act)
Mesh * BKE_mesh_from_bmesh_for_eval_nomain(BMesh *bm, const CustomData_MeshMasks *cd_mask_extra, const Mesh *me_settings)
BMesh * BKE_mesh_to_bmesh_ex(const Mesh *mesh, const BMeshCreateParams *create_params, const BMeshFromMeshParams *convert_params)
void BKE_mesh_wrapper_ensure_mdata(Mesh *mesh)
General operations, lookup, etc. for blender objects.
Mesh * BKE_object_get_evaluated_mesh(const Object *object_eval)
#define CLOG_DEBUG(clg_ref,...)
Definition CLG_log.h:191
struct CustomDataLayer CustomDataLayer
@ CD_PROP_BYTE_COLOR
@ CD_PROP_FLOAT3
@ CD_PROP_FLOAT2
struct Material Material
struct Mesh Mesh
@ eModifierMode_DisableTemporary
@ eModifierType_Subsurf
Object is a sort of wrapper for general info.
struct Object Object
BMesh * bm
void BM_mesh_free(BMesh *bm)
BMesh Free Mesh.
ATTR_WARN_UNUSED_RESULT const BMVert const BMEdge * e
void BM_mesh_triangulate(BMesh *bm, const int quad_method, const int ngon_method, const int min_vertices, const bool tag_only, BMOperator *op, BMOpSlot *slot_facemap_out, BMOpSlot *slot_facemap_double_out)
static AttributeOwner from_id(ID *id)
Definition attribute.cc:44
AttributeSet attributes
constexpr int64_t size() const
constexpr int64_t start() const
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
GAttributeReader lookup(const StringRef attribute_id) const
ABCAbstractWriter(const ABCWriterConstructorArgs &args)
Alembic::Abc::OCompoundProperty abc_schema_prop_for_custom_props(T abc_schema)
const ABCWriterConstructorArgs args_
virtual void update_bounding_box(Object *object)
void do_write(HierarchyContext &context) override
ABCGenericMeshWriter(const ABCWriterConstructorArgs &args)
Alembic::Abc::OObject get_alembic_object() const override
bool is_supported(const HierarchyContext *context) const override
virtual Mesh * get_export_mesh(Object *object_eval, bool &r_needsfree)=0
Alembic::Abc::OCompoundProperty abc_prop_for_custom_props() override
void create_alembic_objects(const HierarchyContext *context) override
virtual bool export_as_subdivision_surface(Object *ob_eval) const
Mesh * get_export_mesh(Object *object_eval, bool &r_needsfree) override
ABCMeshWriter(const ABCWriterConstructorArgs &args)
nullptr float
static ushort indices[]
static float normals[][3]
#define LOG(level)
Definition log.h:97
static char faces[256]
BLI_INLINE float crease_to_sharpness(float crease)
int context(const bContext *C, const char *member, bContextDataResult *result)
BLI_INLINE void copy_yup_from_zup(float yup[3], const float zup[3])
static void get_vert_creases(Mesh *mesh, std::vector< int32_t > &indices, std::vector< float > &sharpnesses)
static void get_edge_creases(Mesh *mesh, std::vector< int32_t > &indices, std::vector< int32_t > &lengths, std::vector< float > &sharpnesses)
const char * get_uv_sample(UVSample &sample, const CDStreamConfig &config, CustomData *data)
void write_custom_data(const OCompoundProperty &prop, CDStreamConfig &config, CustomData *data, int data_type)
static void get_loop_normals(const Mesh *mesh, std::vector< Imath::V3f > &normals)
static void get_topology(Mesh *mesh, std::vector< int32_t > &face_verts, std::vector< int32_t > &loop_counts)
void write_generated_coordinates(const OCompoundProperty &prop, CDStreamConfig &config)
static void get_vertices(Mesh *mesh, std::vector< Imath::V3f > &points)
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
Definition BLI_task.hh:93
VecBase< float, 3 > float3
const char * name
void * last
int corners_num
CustomData corner_data
int totface_legacy
int faces_num
int verts_num
struct ModifierData * prev
ListBase modifiers
i
Definition text_draw.cc:230