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
8
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{
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_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{
129 if (args_.export_params->visible_objects_only) {
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
149 if (args_.export_params->triangulate) {
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
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
255 if (args_.export_params->orcos) {
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
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
283 if (!frame_has_been_written_ && args_.export_params->face_sets) {
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
310 if (args_.export_params->orcos) {
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
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{
349 if (!args_.export_params->vcolors) {
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
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)
Definition attribute.cc:611
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
struct CustomDataLayer CustomDataLayer
@ CD_PROP_BYTE_COLOR
@ CD_PROP_FLOAT3
@ CD_PROP_FLOAT2
struct Material Material
struct Mesh Mesh
@ eModifierMode_DisableTemporary
@ eModifierType_Subsurf
struct Object Object
in reality light always falls off quadratically Particle Retrieve the data of the particle that spawned the object for example to give variation to multiple instances of an object Point Retrieve information about points in a point cloud Retrieve the edges of an object as it appears to Cycles topology will always appear triangulated Convert a blackbody temperature to an RGB value Normal Generate a perturbed normal from an RGB normal map image Typically used for faking highly detailed surfaces Generate an OSL shader from a file or text data block Image Sample an image file as a texture Gabor Generate Gabor noise Gradient Generate interpolated color and intensity values based on the input vector Magic Generate a psychedelic color texture Voronoi Generate Worley noise based on the distance to random points Typically used to generate textures such as or biological cells Brick Generate a procedural texture producing bricks Texture Retrieve multiple types of texture coordinates nTypically used as inputs for texture nodes Vector Convert a or normal between and object coordinate space Combine Create a color from its and value channels Color Retrieve a color attribute
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
AttributeSet attributes
constexpr int64_t size() const
constexpr int64_t start() const
constexpr int64_t size() const
Definition BLI_span.hh:253
constexpr IndexRange index_range() const
Definition BLI_span.hh:402
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)
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
ABCMeshWriter(const ABCWriterConstructorArgs &args)
draw_view in_light_buf[] float
static ushort indices[]
static float normals[][3]
#define LOG(severity)
Definition log.h:33
static char faces[256]
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:95
VecBase< float, 3 > float3
void * last
int corners_num
CustomData corner_data
int totface_legacy
int faces_num
int verts_num
struct ModifierData * prev
ListBase modifiers