Blender V4.3
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
9#include "abc_writer_mesh.h"
12
13#include "BLI_math_vector.h"
14
15#include "BKE_attribute.hh"
16#include "BKE_lib_id.hh"
17#include "BKE_material.h"
18#include "BKE_mesh.hh"
19#include "BKE_mesh_wrapper.hh"
20#include "BKE_object.hh"
21
22#include "bmesh.hh"
23#include "bmesh_tools.hh"
24
26#include "DNA_material_types.h"
27#include "DNA_mesh_types.h"
28#include "DNA_modifier_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{
78 }
79
80 if (is_subd_) {
81 CLOG_INFO(&LOG, 2, "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_INFO(&LOG, 2, "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{
130 return context->is_object_visible(args_.export_params->evaluation_mode);
131 }
132 return true;
133}
134
136{
137 Object *object = context.object;
138 bool needsfree = false;
139
140 Mesh *mesh = get_export_mesh(object, needsfree);
141
142 if (mesh == nullptr) {
143 return;
144 }
145
146 /* Ensure data exists if currently in edit mode. */
148
150 const bool tag_only = false;
151 const int quad_method = args_.export_params->quad_method;
152 const int ngon_method = args_.export_params->ngon_method;
153
154 BMeshCreateParams bmesh_create_params{};
155 BMeshFromMeshParams bmesh_from_mesh_params{};
156 bmesh_from_mesh_params.calc_face_normal = true;
157 bmesh_from_mesh_params.calc_vert_normal = true;
158 BMesh *bm = BKE_mesh_to_bmesh_ex(mesh, &bmesh_create_params, &bmesh_from_mesh_params);
159
160 BM_mesh_triangulate(bm, quad_method, ngon_method, 4, tag_only, nullptr, nullptr, nullptr);
161
162 Mesh *triangulated_mesh = BKE_mesh_from_bmesh_for_eval_nomain(bm, nullptr, mesh);
164
165 if (needsfree) {
166 free_export_mesh(mesh);
167 }
168 mesh = triangulated_mesh;
169 needsfree = true;
170 }
171
172 m_custom_data_config.pack_uvs = args_.export_params->packuv;
173 m_custom_data_config.mesh = mesh;
174 m_custom_data_config.face_offsets = mesh->face_offsets_for_write().data();
175 m_custom_data_config.corner_verts = mesh->corner_verts_for_write().data();
176 m_custom_data_config.faces_num = mesh->faces_num;
177 m_custom_data_config.totloop = mesh->corners_num;
178 m_custom_data_config.totvert = mesh->verts_num;
179 m_custom_data_config.timesample_index = timesample_index_;
180
181 try {
182 if (is_subd_) {
183 write_subd(context, mesh);
184 }
185 else {
186 write_mesh(context, mesh);
187 }
188
189 if (needsfree) {
190 free_export_mesh(mesh);
191 }
192 }
193 catch (...) {
194 if (needsfree) {
195 free_export_mesh(mesh);
196 }
197 throw;
198 }
199}
200
202{
203 BKE_id_free(nullptr, mesh);
204}
205
206void ABCGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh)
207{
208 std::vector<Imath::V3f> points, normals;
209 std::vector<int32_t> face_verts, loop_counts;
210 std::vector<Imath::V3f> velocities;
211
212 get_vertices(mesh, points);
213 get_topology(mesh, face_verts, loop_counts);
214
216 write_face_sets(context.object, mesh, abc_poly_mesh_schema_);
217 }
218
219 OPolyMeshSchema::Sample mesh_sample = OPolyMeshSchema::Sample(
220 V3fArraySample(points), Int32ArraySample(face_verts), Int32ArraySample(loop_counts));
221
222 UVSample uvs_and_indices;
223
224 if (args_.export_params->uvs) {
225 const char *name = get_uv_sample(uvs_and_indices, m_custom_data_config, &mesh->corner_data);
226
227 if (!uvs_and_indices.indices.empty() && !uvs_and_indices.uvs.empty()) {
228 OV2fGeomParam::Sample uv_sample;
229 uv_sample.setVals(V2fArraySample(uvs_and_indices.uvs));
230 uv_sample.setIndices(UInt32ArraySample(uvs_and_indices.indices));
231 uv_sample.setScope(kFacevaryingScope);
232
233 abc_poly_mesh_schema_.setUVSourceName(name);
234 mesh_sample.setUVs(uv_sample);
235 }
236
237 write_custom_data(abc_poly_mesh_schema_.getArbGeomParams(),
238 m_custom_data_config,
239 &mesh->corner_data,
241 }
242
244 get_loop_normals(mesh, normals);
245
246 ON3fGeomParam::Sample normals_sample;
247 if (!normals.empty()) {
248 normals_sample.setScope(kFacevaryingScope);
249 normals_sample.setVals(V3fArraySample(normals));
250 }
251
252 mesh_sample.setNormals(normals_sample);
253 }
254
256 write_generated_coordinates(abc_poly_mesh_schema_.getArbGeomParams(), m_custom_data_config);
257 }
258
259 if (get_velocities(mesh, velocities)) {
260 mesh_sample.setVelocities(V3fArraySample(velocities));
261 }
262
263 update_bounding_box(context.object);
264 mesh_sample.setSelfBounds(bounding_box_);
265
266 abc_poly_mesh_schema_.set(mesh_sample);
267
268 write_arb_geo_params(mesh);
269}
270
271void ABCGenericMeshWriter::write_subd(HierarchyContext &context, Mesh *mesh)
272{
273 std::vector<float> edge_crease_sharpness, vert_crease_sharpness;
274 std::vector<Imath::V3f> points;
275 std::vector<int32_t> face_verts, loop_counts;
276 std::vector<int32_t> edge_crease_indices, edge_crease_lengths, vert_crease_indices;
277
278 get_vertices(mesh, points);
279 get_topology(mesh, face_verts, loop_counts);
280 get_edge_creases(mesh, edge_crease_indices, edge_crease_lengths, edge_crease_sharpness);
281 get_vert_creases(mesh, vert_crease_indices, vert_crease_sharpness);
282
284 write_face_sets(context.object, mesh, abc_subdiv_schema_);
285 }
286
287 OSubDSchema::Sample subdiv_sample = OSubDSchema::Sample(
288 V3fArraySample(points), Int32ArraySample(face_verts), Int32ArraySample(loop_counts));
289
290 UVSample sample;
291 if (args_.export_params->uvs) {
292 const char *name = get_uv_sample(sample, m_custom_data_config, &mesh->corner_data);
293
294 if (!sample.indices.empty() && !sample.uvs.empty()) {
295 OV2fGeomParam::Sample uv_sample;
296 uv_sample.setVals(V2fArraySample(sample.uvs));
297 uv_sample.setIndices(UInt32ArraySample(sample.indices));
298 uv_sample.setScope(kFacevaryingScope);
299
300 abc_subdiv_schema_.setUVSourceName(name);
301 subdiv_sample.setUVs(uv_sample);
302 }
303
304 write_custom_data(abc_subdiv_schema_.getArbGeomParams(),
305 m_custom_data_config,
306 &mesh->corner_data,
308 }
309
311 write_generated_coordinates(abc_subdiv_schema_.getArbGeomParams(), m_custom_data_config);
312 }
313
314 if (!edge_crease_indices.empty()) {
315 subdiv_sample.setCreaseIndices(Int32ArraySample(edge_crease_indices));
316 subdiv_sample.setCreaseLengths(Int32ArraySample(edge_crease_lengths));
317 subdiv_sample.setCreaseSharpnesses(FloatArraySample(edge_crease_sharpness));
318 }
319
320 if (!vert_crease_indices.empty()) {
321 subdiv_sample.setCornerIndices(Int32ArraySample(vert_crease_indices));
322 subdiv_sample.setCornerSharpnesses(FloatArraySample(vert_crease_sharpness));
323 }
324
325 update_bounding_box(context.object);
326 subdiv_sample.setSelfBounds(bounding_box_);
327 abc_subdiv_schema_.set(subdiv_sample);
328
329 write_arb_geo_params(mesh);
330}
331
332template<typename Schema>
333void ABCGenericMeshWriter::write_face_sets(Object *object, Mesh *mesh, Schema &schema)
334{
335 std::map<std::string, std::vector<int32_t>> geo_groups;
336 get_geo_groups(object, mesh, geo_groups);
337
338 std::map<std::string, std::vector<int32_t>>::iterator it;
339 for (it = geo_groups.begin(); it != geo_groups.end(); ++it) {
340 OFaceSet face_set = schema.createFaceSet(it->first);
341 OFaceSetSchema::Sample samp;
342 samp.setFaces(Int32ArraySample(it->second));
343 face_set.getSchema().set(samp);
344 }
345}
346
347void ABCGenericMeshWriter::write_arb_geo_params(Mesh *mesh)
348{
350 return;
351 }
352
353 OCompoundProperty arb_geom_params;
354 if (is_subd_) {
355 arb_geom_params = abc_subdiv_.getSchema().getArbGeomParams();
356 }
357 else {
358 arb_geom_params = abc_poly_mesh_.getSchema().getArbGeomParams();
359 }
360 write_custom_data(arb_geom_params, m_custom_data_config, &mesh->corner_data, CD_PROP_BYTE_COLOR);
361}
362
363bool ABCGenericMeshWriter::get_velocities(Mesh *mesh, std::vector<Imath::V3f> &vels)
364{
365 /* Export velocity attribute output by fluid sim, sequence cache modifier
366 * and geometry nodes. */
367 AttributeOwner owner = AttributeOwner::from_id(&mesh->id);
368 const CustomDataLayer *velocity_layer = BKE_attribute_find(
369 owner, "velocity", CD_PROP_FLOAT3, bke::AttrDomain::Point);
370
371 if (velocity_layer == nullptr) {
372 return false;
373 }
374
375 const int totverts = mesh->verts_num;
376 const float(*mesh_velocities)[3] = reinterpret_cast<float(*)[3]>(velocity_layer->data);
377
378 vels.clear();
379 vels.resize(totverts);
380
381 for (int i = 0; i < totverts; i++) {
382 copy_yup_from_zup(vels[i].getValue(), mesh_velocities[i]);
383 }
384
385 return true;
386}
387
388void ABCGenericMeshWriter::get_geo_groups(Object *object,
389 Mesh *mesh,
390 std::map<std::string, std::vector<int32_t>> &geo_groups)
391{
392 const bke::AttributeAccessor attributes = mesh->attributes();
393 const VArraySpan<int> material_indices = *attributes.lookup_or_default<int>(
394 "material_index", bke::AttrDomain::Face, 0);
395
396 for (const int i : material_indices.index_range()) {
397 short mnr = material_indices[i];
398
399 Material *mat = BKE_object_material_get(object, mnr + 1);
400
401 if (!mat) {
402 continue;
403 }
404
405 std::string name = args_.hierarchy_iterator->get_id_name(&mat->id);
406
407 if (geo_groups.find(name) == geo_groups.end()) {
408 std::vector<int32_t> faceArray;
409 geo_groups[name] = faceArray;
410 }
411
412 geo_groups[name].push_back(i);
413 }
414
415 if (geo_groups.empty()) {
416 Material *mat = BKE_object_material_get(object, 1);
417
418 std::string name = (mat) ? args_.hierarchy_iterator->get_id_name(&mat->id) : "default";
419
420 std::vector<int32_t> faceArray;
421
422 for (int i = 0, e = mesh->totface_legacy; i < e; i++) {
423 faceArray.push_back(i);
424 }
425
426 geo_groups[name] = faceArray;
427 }
428}
429
430/* NOTE: Alembic's polygon winding order is clockwise, to match with Renderman. */
431
432static void get_vertices(Mesh *mesh, std::vector<Imath::V3f> &points)
433{
434 points.clear();
435 points.resize(mesh->verts_num);
436
437 const Span<float3> positions = mesh->vert_positions();
438 for (int i = 0, e = mesh->verts_num; i < e; i++) {
439 copy_yup_from_zup(points[i].getValue(), positions[i]);
440 }
441}
442
443static void get_topology(Mesh *mesh,
444 std::vector<int32_t> &face_verts,
445 std::vector<int32_t> &loop_counts)
446{
447 const OffsetIndices faces = mesh->faces();
448 const Span<int> corner_verts = mesh->corner_verts();
449
450 face_verts.clear();
451 loop_counts.clear();
452 face_verts.reserve(corner_verts.size());
453 loop_counts.reserve(faces.size());
454
455 /* NOTE: data needs to be written in the reverse order. */
456 for (const int i : faces.index_range()) {
457 const IndexRange face = faces[i];
458 loop_counts.push_back(face.size());
459
460 int corner = face.start() + (face.size() - 1);
461 for (int j = 0; j < face.size(); j++, corner--) {
462 face_verts.push_back(corner_verts[corner]);
463 }
464 }
465}
466
467static void get_edge_creases(Mesh *mesh,
468 std::vector<int32_t> &indices,
469 std::vector<int32_t> &lengths,
470 std::vector<float> &sharpnesses)
471{
472 indices.clear();
473 lengths.clear();
474 sharpnesses.clear();
475
476 const bke::AttributeAccessor attributes = mesh->attributes();
477 const bke::AttributeReader attribute = attributes.lookup<float>("crease_edge",
479 if (!attribute) {
480 return;
481 }
482 const VArraySpan creases(*attribute);
483 const Span<int2> edges = mesh->edges();
484 for (const int i : edges.index_range()) {
485 const float sharpness = creases[i];
486
487 if (sharpness != 0.0f) {
488 indices.push_back(edges[i][0]);
489 indices.push_back(edges[i][1]);
490 sharpnesses.push_back(sharpness);
491 }
492 }
493
494 lengths.resize(sharpnesses.size(), 2);
495}
496
497static void get_vert_creases(Mesh *mesh,
498 std::vector<int32_t> &indices,
499 std::vector<float> &sharpnesses)
500{
501 indices.clear();
502 sharpnesses.clear();
503
504 const bke::AttributeAccessor attributes = mesh->attributes();
505 const bke::AttributeReader attribute = attributes.lookup<float>("crease_vert",
507 if (!attribute) {
508 return;
509 }
510 const VArraySpan creases(*attribute);
511 for (const int i : creases.index_range()) {
512 const float sharpness = creases[i];
513
514 if (sharpness != 0.0f) {
515 indices.push_back(i);
516 sharpnesses.push_back(sharpness);
517 }
518 }
519}
520
521static void get_loop_normals(const Mesh *mesh, std::vector<Imath::V3f> &normals)
522{
523 normals.clear();
524
525 switch (mesh->normals_domain()) {
527 /* If all faces are smooth shaded, and there are no custom normals, we don't need to
528 * export normals at all. This is also done by other software, see #71246. */
529 break;
530 }
532 normals.resize(mesh->corners_num);
533 MutableSpan dst_normals(reinterpret_cast<float3 *>(normals.data()), normals.size());
534
535 const OffsetIndices faces = mesh->faces();
536 const Span<float3> face_normals = mesh->face_normals();
537 threading::parallel_for(faces.index_range(), 1024, [&](const IndexRange range) {
538 for (const int i : range) {
539 float3 y_up;
540 copy_yup_from_zup(y_up, face_normals[i]);
541 dst_normals.slice(faces[i]).fill(y_up);
542 }
543 });
544 break;
545 }
547 normals.resize(mesh->corners_num);
548 MutableSpan dst_normals(reinterpret_cast<float3 *>(normals.data()), normals.size());
549
550 /* NOTE: data needs to be written in the reverse order. */
551 const OffsetIndices faces = mesh->faces();
552 const Span<float3> corner_normals = mesh->corner_normals();
553 threading::parallel_for(faces.index_range(), 1024, [&](const IndexRange range) {
554 for (const int i : range) {
555 const IndexRange face = faces[i];
556 for (const int i : face.index_range()) {
557 copy_yup_from_zup(dst_normals[face.last(i)], corner_normals[face[i]]);
558 }
559 }
560 });
561 break;
562 }
563 }
564}
565
566ABCMeshWriter::ABCMeshWriter(const ABCWriterConstructorArgs &args) : ABCGenericMeshWriter(args) {}
567
568Mesh *ABCMeshWriter::get_export_mesh(Object *object_eval, bool & /*r_needsfree*/)
569{
570 return BKE_object_get_evaluated_mesh(object_eval);
571}
572
573} // namespace blender::io::alembic
struct CustomDataLayer * BKE_attribute_find(const AttributeOwner &owner, const char *name, eCustomDataType type, blender::bke::AttrDomain domain)
void BKE_id_free(Main *bmain, void *idv)
General operations, lookup, etc. for materials.
struct Material * BKE_object_material_get(struct 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_INFO(clg_ref, level,...)
Definition CLG_log.h:179
@ CD_PROP_BYTE_COLOR
@ CD_PROP_FLOAT3
@ CD_PROP_FLOAT2
@ eModifierMode_DisableTemporary
@ eModifierType_Subsurf
static CLG_LogRef LOG
ATTR_WARN_UNUSED_RESULT 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:43
constexpr int64_t size() const
Definition BLI_span.hh:253
constexpr IndexRange index_range() const
Definition BLI_span.hh:402
virtual std::string get_id_name(const ID *id) const
Alembic::Abc::OCompoundProperty abc_schema_prop_for_custom_props(T abc_schema)
const ABCWriterConstructorArgs args_
virtual void update_bounding_box(Object *object)
virtual void do_write(HierarchyContext &context) override
ABCGenericMeshWriter(const ABCWriterConstructorArgs &args)
virtual Alembic::Abc::OObject get_alembic_object() const override
virtual 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
virtual void create_alembic_objects(const HierarchyContext *context) override
virtual bool export_as_subdivision_surface(Object *ob_eval) const
virtual Mesh * get_export_mesh(Object *object_eval, bool &r_needsfree) override
draw_view in_light_buf[] float
static float normals[][3]
#define LOG(severity)
Definition log.h:33
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:95
enum eEvaluationMode evaluation_mode
Definition ABC_alembic.h:54
void * last
struct ModifierData * prev
ListBase modifiers