WIP: use generic copy-on-write system to avoid unnecessary data copies #104470

Closed
Jacques Lucke wants to merge 50 commits from JacquesLucke/blender:temp-copy-on-write-customdata into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
440 changed files with 4729 additions and 590 deletions
Showing only changes of commit 3f3adef4a0 - Show all commits

View File

@ -5801,7 +5801,7 @@ print_info() {
PRINT "If you're using CMake add this to your configuration flags:"
_buildargs="-U *SNDFILE* -U PYTHON* -U *BOOST* -U *Boost* -U *TBB*"
_buildargs="$_buildargs -U *OPENCOLORIO* -U *OPENEXR* -U *OPENIMAGEIO* -U *LLVM* -U *CYCLES*"
_buildargs="$_buildargs -U *OPENCOLORIO* -U *OPENEXR* -U *OPENIMAGEIO* -U *LLVM* -U *CLANG* -U *CYCLES*"
_buildargs="$_buildargs -U *OPENSUBDIV* -U *OPENVDB* -U *BLOSC* -U *COLLADA* -U *FFMPEG* -U *ALEMBIC* -U *USD*"
_buildargs="$_buildargs -U *EMBREE* -U *OPENIMAGEDENOISE* -U *OPENXR*"

View File

@ -202,7 +202,7 @@ enum SamplingPattern {
/* These flags values correspond to `raytypes` in `osl.cpp`, so keep them in sync! */
enum PathRayFlag {
enum PathRayFlag : uint32_t {
/* --------------------------------------------------------------------
* Ray visibility.
*
@ -1559,7 +1559,7 @@ enum {
/* Kernel Features */
enum KernelFeatureFlag : unsigned int {
enum KernelFeatureFlag : uint32_t {
/* Shader nodes. */
KERNEL_FEATURE_NODE_BSDF = (1U << 0U),
KERNEL_FEATURE_NODE_EMISSION = (1U << 1U),

View File

@ -5901,7 +5901,7 @@ VectorMapRangeNode::VectorMapRangeNode() : ShaderNode(get_node_type())
{
}
void VectorMapRangeNode::expand(ShaderGraph *graph)
void VectorMapRangeNode::expand(ShaderGraph * /*graph*/)
{
}

View File

@ -100,6 +100,7 @@ LANGUAGES = (
(45, "Abkhaz (Аԥсуа бызшәа)", "ab"),
(46, "Thai (ภาษาไทย)", "th_TH"),
(47, "Slovak (Slovenčina)", "sk_SK"),
(48, "Georgian (ქართული)", "ka"),
)
# Default context, in py (keep in sync with `BLT_translation.h`)!

View File

@ -1135,6 +1135,7 @@ class I18nMessages:
# XXX Temp solution, until I can make own mo generator working...
import subprocess
with tempfile.NamedTemporaryFile(mode='w+', encoding="utf-8") as tmp_po_f:
os.makedirs(os.path.dirname(fname), exist_ok=True)
self.write_messages_to_po(tmp_po_f)
cmd = (
self.settings.GETTEXT_MSGFMT_EXECUTABLE,

View File

@ -481,6 +481,7 @@ class TOPBAR_MT_file_export(Menu):
bl_owner_use_filter = False
def draw(self, _context):
self.layout.operator("wm.obj_export", text="Wavefront OBJ (.obj) - New")
if bpy.app.build_options.collada:
self.layout.operator("wm.collada_export",
text="Collada (Default) (.dae)")

View File

@ -150,6 +150,7 @@ def mesh_node_items(context):
yield NodeItem("GeometryNodeSubdivisionSurface")
yield NodeItem("GeometryNodeTriangulate")
yield NodeItemCustom(draw=lambda self, layout, context: layout.separator())
yield NodeItem("GeometryNodeInputMeshEdgeAngle")
yield NodeItem("GeometryNodeInputMeshEdgeNeighbors")
yield NodeItem("GeometryNodeInputMeshEdgeVertices")
yield NodeItem("GeometryNodeInputMeshFaceArea")

View File

@ -930,9 +930,10 @@ class InstancesComponent : public GeometryComponent {
int references_amount() const;
/**
* Remove the indices in the selection mask and remove unused instance references afterwards.
* Remove the indices that are not contained in the mask input, and remove unused instance
* references afterwards.
*/
void remove_instances(const blender::IndexMask selection);
void remove_instances(const blender::IndexMask mask);
blender::Span<int> almost_unique_ids() const;

View File

@ -969,9 +969,11 @@ bool nodeGroupPoll(struct bNodeTree *nodetree,
/**
* Initialize a new node type struct with default values and callbacks.
*/
void node_type_base(struct bNodeType *ntype, int type, const char *name, short nclass, short flag);
void node_type_base_custom(
struct bNodeType *ntype, const char *idname, const char *name, short nclass, short flag);
void node_type_base(struct bNodeType *ntype, int type, const char *name, short nclass);
void node_type_base_custom(struct bNodeType *ntype,
const char *idname,
const char *name,
short nclass);
void node_type_socket_templates(struct bNodeType *ntype,
struct bNodeSocketTemplate *inputs,
struct bNodeSocketTemplate *outputs);
@ -1707,6 +1709,7 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
#define GEO_NODE_INPUT_MESH_ISLAND 1144
#define GEO_NODE_INPUT_SCENE_TIME 1145
#define GEO_NODE_ACCUMULATE_FIELD 1146
#define GEO_NODE_INPUT_MESH_EDGE_ANGLE 1147
/** \} */

View File

@ -148,27 +148,27 @@ static void copy_data_based_on_mask(Span<T> src, MutableSpan<T> dst, IndexMask m
});
}
void InstancesComponent::remove_instances(const IndexMask selection)
void InstancesComponent::remove_instances(const IndexMask mask)
{
using namespace blender;
if (selection.is_range() && selection.index_range().first() == 0) {
if (mask.is_range() && mask.as_range().start() == 0) {
/* Deleting from the end of the array can be much faster since no data has to be shifted. */
this->resize(selection.size());
this->resize(mask.size());
this->remove_unused_references();
return;
}
Vector<int> new_handles(selection.size());
copy_data_based_on_mask<int>(this->instance_reference_handles(), new_handles, selection);
Vector<int> new_handles(mask.size());
copy_data_based_on_mask<int>(this->instance_reference_handles(), new_handles, mask);
instance_reference_handles_ = std::move(new_handles);
Vector<float4x4> new_transforms(selection.size());
copy_data_based_on_mask<float4x4>(this->instance_transforms(), new_transforms, selection);
Vector<float4x4> new_transforms(mask.size());
copy_data_based_on_mask<float4x4>(this->instance_transforms(), new_transforms, mask);
instance_transforms_ = std::move(new_transforms);
const bke::CustomDataAttributes &src_attributes = attributes_;
bke::CustomDataAttributes dst_attributes;
dst_attributes.reallocate(selection.size());
dst_attributes.reallocate(mask.size());
src_attributes.foreach_attribute(
[&](const bke::AttributeIDRef &id, const AttributeMetaData &meta_data) {
@ -182,7 +182,7 @@ void InstancesComponent::remove_instances(const IndexMask selection)
attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
using T = decltype(dummy);
copy_data_based_on_mask<T>(src.typed<T>(), dst.typed<T>(), selection);
copy_data_based_on_mask<T>(src.typed<T>(), dst.typed<T>(), mask);
});
return true;
},

View File

@ -4357,8 +4357,7 @@ static bool node_poll_instance_default(bNode *node, bNodeTree *ntree, const char
return node->typeinfo->poll(node->typeinfo, ntree, disabled_hint);
}
/* NOLINTNEXTLINE: readability-function-size */
void node_type_base(bNodeType *ntype, int type, const char *name, short nclass, short flag)
void node_type_base(bNodeType *ntype, int type, const char *name, short nclass)
{
/* Use static type info header to map static int type to identifier string and RNA struct type.
* Associate the RNA struct type with the bNodeType.
@ -4385,7 +4384,6 @@ void node_type_base(bNodeType *ntype, int type, const char *name, short nclass,
ntype->type = type;
BLI_strncpy(ntype->ui_name, name, sizeof(ntype->ui_name));
ntype->nclass = nclass;
ntype->flag = flag;
node_type_base_defaults(ntype);
@ -4393,14 +4391,12 @@ void node_type_base(bNodeType *ntype, int type, const char *name, short nclass,
ntype->poll_instance = node_poll_instance_default;
}
void node_type_base_custom(
bNodeType *ntype, const char *idname, const char *name, short nclass, short flag)
void node_type_base_custom(bNodeType *ntype, const char *idname, const char *name, short nclass)
{
BLI_strncpy(ntype->idname, idname, sizeof(ntype->idname));
ntype->type = NODE_CUSTOM;
BLI_strncpy(ntype->ui_name, name, sizeof(ntype->ui_name));
ntype->nclass = nclass;
ntype->flag = flag;
node_type_base_defaults(ntype);
}
@ -4576,7 +4572,7 @@ static void register_undefined_types()
strcpy(NodeTreeTypeUndefined.ui_name, N_("Undefined"));
strcpy(NodeTreeTypeUndefined.ui_description, N_("Undefined Node Tree Type"));
node_type_base_custom(&NodeTypeUndefined, "NodeUndefined", "Undefined", 0, 0);
node_type_base_custom(&NodeTypeUndefined, "NodeUndefined", "Undefined", 0);
NodeTypeUndefined.poll = node_undefined_poll;
BLI_strncpy(NodeSocketTypeUndefined.idname,
@ -4927,6 +4923,7 @@ static void registerGeometryNodes()
register_node_type_geo_input_index();
register_node_type_geo_input_material_index();
register_node_type_geo_input_material();
register_node_type_geo_input_mesh_edge_angle();
register_node_type_geo_input_mesh_edge_neighbors();
register_node_type_geo_input_mesh_edge_vertices();
register_node_type_geo_input_mesh_face_area();

View File

@ -599,6 +599,11 @@ template<typename T> class MutableSpan {
return MutableSpan(data_ + start, new_size);
}
constexpr MutableSpan slice(IndexRange range) const
{
return this->slice(range.start(), range.size());
}
/**
* Returns a new MutableSpan with n elements removed from the beginning. This invokes
* undefined behavior when n is negative.

View File

@ -382,7 +382,7 @@ MINLINE void blend_color_pinlight_byte(uchar dst[4], const uchar src1[4], const
else {
temp = min_ii(2 * src2[i], src1[i]);
}
dst[i] = (uchar)((temp * fac + src1[i] * mfac) / 255);
dst[i] = (uchar)((min_ii(temp, 255) * fac + src1[i] * mfac) / 255);
}
}
else {
@ -473,7 +473,7 @@ MINLINE void blend_color_exclusion_byte(uchar dst[4], const uchar src1[4], const
int i = 3;
while (i--) {
const int temp = 127 - ((2 * (src1[i] - 127) * (src2[i] - 127)) / 255);
const int temp = 127 - min_ii(((2 * (src1[i] - 127) * (src2[i] - 127)) / 255), 127);
dst[i] = (uchar)((temp * fac + src1[i] * mfac) / 255);
}
}
@ -896,15 +896,9 @@ MINLINE void blend_color_softlight_float(float dst[4], const float src1[4], cons
int i = 3;
while (i--) {
float temp;
if (src1[i] < 0.5f) {
temp = (src2[i] + 0.5f) * src1[i];
}
else {
temp = 1.0f - ((1.0f - (src2[i] + 0.5f)) * (1.0f - src1[i]));
}
dst[i] = (temp * fac + src1[i] * mfac);
float screen = 1.0f - (1.0f - src1[i]) * (1.0f - src2[i]);
float soft_light = ((1.0f - src1[i]) * src2[i] + screen) * src1[i];
dst[i] = src1[i] * mfac + soft_light * fac;
}
}
else {

View File

@ -26,6 +26,7 @@ set(INC
../blentranslation
../depsgraph
../draw
../editors/include
../imbuf
../makesdna
../makesrna

View File

@ -2509,5 +2509,22 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain)
}
}
FOREACH_NODETREE_END;
/* Update spreadsheet data set region type. */
LISTBASE_FOREACH (bScreen *, screen, &bmain->screens) {
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) {
if (sl->spacetype == SPACE_SPREADSHEET) {
ListBase *regionbase = (sl == area->spacedata.first) ? &area->regionbase :
&sl->regionbase;
LISTBASE_FOREACH (ARegion *, region, regionbase) {
if (region->regiontype == RGN_TYPE_CHANNELS) {
region->regiontype = RGN_TYPE_TOOLS;
}
}
}
}
}
}
}
}
}

View File

@ -27,9 +27,11 @@
#include "BKE_idtype.h"
#include "BKE_image.h"
#include "BKE_main.h"
#include "BKE_mball_tessellate.h"
#include "BKE_modifier.h"
#include "BKE_node.h"
#include "BKE_scene.h"
#include "BKE_vfont.h"
#include "BLI_path_util.h"
#include "BLI_threads.h"
@ -44,6 +46,8 @@
#include "IMB_imbuf.h"
#include "ED_datafiles.h"
#include "RNA_define.h"
#include "WM_api.h"
@ -73,6 +77,7 @@ void BlendfileLoadingBaseTest::SetUpTestCase()
RNA_init();
BKE_node_system_init();
BKE_callback_global_init();
BKE_vfont_builtin_register(datatoc_bfont_pfb, datatoc_bfont_pfb_size);
G.background = true;
G.factory_startup = true;
@ -111,6 +116,7 @@ void BlendfileLoadingBaseTest::TearDownTestCase()
void BlendfileLoadingBaseTest::TearDown()
{
BKE_mball_cubeTable_free();
depsgraph_free();
blendfile_free();

View File

@ -42,22 +42,22 @@ void CropBaseOperation::update_area()
local_settings.y1 = height * local_settings.fac_y1;
local_settings.y2 = height * local_settings.fac_y2;
}
if (width <= local_settings.x1 + 1) {
local_settings.x1 = width - 1;
if (width < local_settings.x1) {
local_settings.x1 = width;
}
if (height <= local_settings.y1 + 1) {
local_settings.y1 = height - 1;
if (height < local_settings.y1) {
local_settings.y1 = height;
}
if (width <= local_settings.x2 + 1) {
local_settings.x2 = width - 1;
if (width < local_settings.x2) {
local_settings.x2 = width;
}
if (height <= local_settings.y2 + 1) {
local_settings.y2 = height - 1;
if (height < local_settings.y2) {
local_settings.y2 = height;
}
xmax_ = MAX2(local_settings.x1, local_settings.x2) + 1;
xmax_ = MAX2(local_settings.x1, local_settings.x2);
xmin_ = MIN2(local_settings.x1, local_settings.x2);
ymax_ = MAX2(local_settings.y1, local_settings.y2) + 1;
ymax_ = MAX2(local_settings.y1, local_settings.y2);
ymin_ = MIN2(local_settings.y1, local_settings.y2);
}
else {
@ -98,10 +98,8 @@ void CropOperation::update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs)
{
rcti crop_area;
BLI_rcti_init(&crop_area, xmin_, xmax_, ymin_, ymax_);
for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) {
if (BLI_rcti_isect_pt(&crop_area, it.x, it.y)) {
if ((it.x < xmax_ && it.x >= xmin_) && (it.y < ymax_ && it.y >= ymin_)) {
copy_v4_v4(it.out, it.in(0));
}
else {
@ -166,11 +164,11 @@ void CropImageOperation::update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs)
{
rcti op_area;
BLI_rcti_init(&op_area, 0, get_width(), 0, get_height());
const MemoryBuffer *input = inputs[0];
const int width = get_width();
const int height = get_height();
for (BuffersIterator<float> it = output->iterate_with({}, area); !it.is_end(); ++it) {
if (BLI_rcti_isect_pt(&op_area, it.x, it.y)) {
if (it.x >= 0 && it.x < width && it.y >= 0 && it.y < height) {
input->read_elem_checked(it.x + xmin_, it.y + ymin_, it.out);
}
else {

View File

@ -199,9 +199,7 @@ IDNode *DepsgraphNodeBuilder::add_id_node(ID *id)
nullptr, OperationCode::OPERATION, "", -1);
/* Pin the node so that it and its relations are preserved by the unused nodes/relations
* deletion. This is mainly to make it easier to debug visibility. */
/* NOTE: Keep un-pinned for the 3.0 release. This way we are more sure that side effects of the
* change is minimal outside of the dependency graph area. */
// visibility_operation->flag |= OperationFlag::DEPSOP_FLAG_PINNED;
visibility_operation->flag |= OperationFlag::DEPSOP_FLAG_PINNED;
graph_->operations.append(visibility_operation);
}
return id_node;
@ -589,11 +587,7 @@ void DepsgraphNodeBuilder::build_id(ID *id)
case ID_HA:
case ID_PT:
case ID_VO:
/* TODO(sergey): Get visibility from a "parent" somehow.
*
* NOTE: Similarly to above, we don't want false-positives on
* visibility. */
build_object_data_geometry_datablock(id, false);
build_object_data_geometry_datablock(id);
break;
case ID_SPK:
build_speaker((Speaker *)id);
@ -769,32 +763,28 @@ void DepsgraphNodeBuilder::build_object(int base_index,
if (object->modifiers.first != nullptr) {
BuilderWalkUserData data;
data.builder = this;
data.is_parent_visible = is_visible;
BKE_modifiers_foreach_ID_link(object, modifier_walk, &data);
}
/* Grease Pencil Modifiers. */
if (object->greasepencil_modifiers.first != nullptr) {
BuilderWalkUserData data;
data.builder = this;
data.is_parent_visible = is_visible;
BKE_gpencil_modifiers_foreach_ID_link(object, modifier_walk, &data);
}
/* Shader FX. */
if (object->shader_fx.first != nullptr) {
BuilderWalkUserData data;
data.builder = this;
data.is_parent_visible = is_visible;
BKE_shaderfx_foreach_ID_link(object, modifier_walk, &data);
}
/* Constraints. */
if (object->constraints.first != nullptr) {
BuilderWalkUserData data;
data.builder = this;
data.is_parent_visible = is_visible;
BKE_constraints_id_loop(&object->constraints, constraint_walk, &data);
}
/* Object data. */
build_object_data(object, is_visible);
build_object_data(object);
/* Parameters, used by both drivers/animation and also to inform dependency
* from object's data. */
build_parameters(&object->id);
@ -897,7 +887,7 @@ void DepsgraphNodeBuilder::build_object_instance_collection(Object *object, bool
is_parent_collection_visible_ = is_current_parent_collection_visible;
}
void DepsgraphNodeBuilder::build_object_data(Object *object, bool is_object_visible)
void DepsgraphNodeBuilder::build_object_data(Object *object)
{
if (object->data == nullptr) {
return;
@ -914,14 +904,14 @@ void DepsgraphNodeBuilder::build_object_data(Object *object, bool is_object_visi
case OB_HAIR:
case OB_POINTCLOUD:
case OB_VOLUME:
build_object_data_geometry(object, is_object_visible);
build_object_data_geometry(object);
break;
case OB_ARMATURE:
if (ID_IS_LINKED(object) && object->proxy_from != nullptr) {
build_proxy_rig(object, is_object_visible);
build_proxy_rig(object);
}
else {
build_rig(object, is_object_visible);
build_rig(object);
}
break;
case OB_LAMP:
@ -1472,7 +1462,7 @@ void DepsgraphNodeBuilder::build_shapekeys(Key *key)
/* ObData Geometry Evaluation */
/* XXX: what happens if the datablock is shared! */
void DepsgraphNodeBuilder::build_object_data_geometry(Object *object, bool is_object_visible)
void DepsgraphNodeBuilder::build_object_data_geometry(Object *object)
{
OperationNode *op_node;
Scene *scene_cow = get_cow_datablock(scene_);
@ -1494,7 +1484,7 @@ void DepsgraphNodeBuilder::build_object_data_geometry(Object *object, bool is_ob
/* Point caches. */
build_object_pointcache(object);
/* Geometry. */
build_object_data_geometry_datablock((ID *)object->data, is_object_visible);
build_object_data_geometry_datablock((ID *)object->data);
build_dimensions(object);
/* Batch cache. */
add_operation_node(
@ -1504,7 +1494,7 @@ void DepsgraphNodeBuilder::build_object_data_geometry(Object *object, bool is_ob
[object_cow](::Depsgraph *depsgraph) { BKE_object_select_update(depsgraph, object_cow); });
}
void DepsgraphNodeBuilder::build_object_data_geometry_datablock(ID *obdata, bool is_object_visible)
void DepsgraphNodeBuilder::build_object_data_geometry_datablock(ID *obdata)
{
if (built_map_.checkIsBuiltAndTag(obdata)) {
return;
@ -1548,17 +1538,15 @@ void DepsgraphNodeBuilder::build_object_data_geometry_datablock(ID *obdata, bool
BKE_curve_eval_geometry(depsgraph, (Curve *)obdata_cow);
});
op_node->set_as_entry();
/* Make sure objects used for bevel.taper are in the graph.
* NOTE: This objects might be not linked to the scene. */
Curve *cu = (Curve *)obdata;
if (cu->bevobj != nullptr) {
build_object(-1, cu->bevobj, DEG_ID_LINKED_INDIRECTLY, is_object_visible);
build_object(-1, cu->bevobj, DEG_ID_LINKED_INDIRECTLY, false);
}
if (cu->taperobj != nullptr) {
build_object(-1, cu->taperobj, DEG_ID_LINKED_INDIRECTLY, is_object_visible);
build_object(-1, cu->taperobj, DEG_ID_LINKED_INDIRECTLY, false);
}
if (cu->textoncurve != nullptr) {
build_object(-1, cu->textoncurve, DEG_ID_LINKED_INDIRECTLY, is_object_visible);
build_object(-1, cu->textoncurve, DEG_ID_LINKED_INDIRECTLY, false);
}
break;
}
@ -2121,10 +2109,7 @@ void DepsgraphNodeBuilder::modifier_walk(void *user_data,
}
switch (GS(id->name)) {
case ID_OB:
/* Special case for object, so we take owner visibility into
* account. */
data->builder->build_object(
-1, (Object *)id, DEG_ID_LINKED_INDIRECTLY, data->is_parent_visible);
data->builder->build_object(-1, (Object *)id, DEG_ID_LINKED_INDIRECTLY, false);
break;
default:
data->builder->build_id(id);
@ -2144,10 +2129,7 @@ void DepsgraphNodeBuilder::constraint_walk(bConstraint * /*con*/,
}
switch (GS(id->name)) {
case ID_OB:
/* Special case for object, so we take owner visibility into
* account. */
data->builder->build_object(
-1, (Object *)id, DEG_ID_LINKED_INDIRECTLY, data->is_parent_visible);
data->builder->build_object(-1, (Object *)id, DEG_ID_LINKED_INDIRECTLY, false);
break;
default:
data->builder->build_id(id);

View File

@ -186,20 +186,17 @@ class DepsgraphNodeBuilder : public DepsgraphBuilder {
virtual void build_object_flags(int base_index,
Object *object,
eDepsNode_LinkedState_Type linked_state);
virtual void build_object_data(Object *object, bool is_object_visible);
virtual void build_object_data(Object *object);
virtual void build_object_data_camera(Object *object);
virtual void build_object_data_geometry(Object *object, bool is_object_visible);
virtual void build_object_data_geometry_datablock(ID *obdata, bool is_object_visible);
virtual void build_object_data_geometry(Object *object);
virtual void build_object_data_geometry_datablock(ID *obdata);
virtual void build_object_data_light(Object *object);
virtual void build_object_data_lightprobe(Object *object);
virtual void build_object_data_speaker(Object *object);
virtual void build_object_transform(Object *object);
virtual void build_object_constraints(Object *object);
virtual void build_object_pointcache(Object *object);
virtual void build_pose_constraints(Object *object,
bPoseChannel *pchan,
int pchan_index,
bool is_object_visible);
virtual void build_pose_constraints(Object *object, bPoseChannel *pchan, int pchan_index);
virtual void build_rigidbody(Scene *scene);
virtual void build_particle_systems(Object *object, bool is_object_visible);
virtual void build_particle_settings(ParticleSettings *part);
@ -227,8 +224,8 @@ class DepsgraphNodeBuilder : public DepsgraphBuilder {
virtual void build_dimensions(Object *object);
virtual void build_ik_pose(Object *object, bPoseChannel *pchan, bConstraint *con);
virtual void build_splineik_pose(Object *object, bPoseChannel *pchan, bConstraint *con);
virtual void build_rig(Object *object, bool is_object_visible);
virtual void build_proxy_rig(Object *object, bool is_object_visible);
virtual void build_rig(Object *object);
virtual void build_proxy_rig(Object *object);
virtual void build_armature(bArmature *armature);
virtual void build_armature_bones(ListBase *bones);
virtual void build_shapekeys(Key *key);
@ -284,8 +281,6 @@ class DepsgraphNodeBuilder : public DepsgraphBuilder {
struct BuilderWalkUserData {
DepsgraphNodeBuilder *builder;
/* Denotes whether object the walk is invoked from is visible. */
bool is_parent_visible;
};
static void modifier_walk(void *user_data,
struct Object *object,

View File

@ -58,13 +58,11 @@ namespace blender::deg {
void DepsgraphNodeBuilder::build_pose_constraints(Object *object,
bPoseChannel *pchan,
int pchan_index,
bool is_object_visible)
int pchan_index)
{
/* Pull indirect dependencies via constraints. */
BuilderWalkUserData data;
data.builder = this;
data.is_parent_visible = is_object_visible;
BKE_constraints_id_loop(&pchan->constraints, constraint_walk, &data);
/* Create node for constraint stack. */
@ -147,7 +145,7 @@ void DepsgraphNodeBuilder::build_splineik_pose(Object *object,
}
/* Pose/Armature Bones Graph */
void DepsgraphNodeBuilder::build_rig(Object *object, bool is_object_visible)
void DepsgraphNodeBuilder::build_rig(Object *object)
{
bArmature *armature = (bArmature *)object->data;
Scene *scene_cow = get_cow_datablock(scene_);
@ -272,7 +270,7 @@ void DepsgraphNodeBuilder::build_rig(Object *object, bool is_object_visible)
}
/* Build constraints. */
if (pchan->constraints.first != nullptr) {
build_pose_constraints(object, pchan, pchan_index, is_object_visible);
build_pose_constraints(object, pchan, pchan_index);
}
/**
* IK Solvers.
@ -301,14 +299,14 @@ void DepsgraphNodeBuilder::build_rig(Object *object, bool is_object_visible)
}
/* Custom shape. */
if (pchan->custom != nullptr) {
/* TODO(sergey): Use own visibility. */
build_object(-1, pchan->custom, DEG_ID_LINKED_INDIRECTLY, is_object_visible);
/* NOTE: The relation builder will ensure visibility of the custom shape object. */
build_object(-1, pchan->custom, DEG_ID_LINKED_INDIRECTLY, false);
}
pchan_index++;
}
}
void DepsgraphNodeBuilder::build_proxy_rig(Object *object, bool is_object_visible)
void DepsgraphNodeBuilder::build_proxy_rig(Object *object)
{
bArmature *armature = (bArmature *)object->data;
OperationNode *op_node;
@ -356,7 +354,8 @@ void DepsgraphNodeBuilder::build_proxy_rig(Object *object, bool is_object_visibl
/* Custom shape. */
if (pchan->custom != nullptr) {
build_object(-1, pchan->custom, DEG_ID_LINKED_INDIRECTLY, is_object_visible);
/* NOTE: The relation builder will ensure visibility of the custom shape object. */
build_object(-1, pchan->custom, DEG_ID_LINKED_INDIRECTLY, false);
}
pchan_index++;

View File

@ -112,11 +112,11 @@ enum class NodeType {
* which simplifies common algorithms which are dealing with relations and visibility.
*
* The fact that the visibility operates on the ID level basically means that all components in
* NodeA will be considered as affecting directly visible when NodeB's visibility is
* the NodeA will be considered as affecting directly visible when NodeB's visibility is
* affecting directly visible ID.
*
* This is the way to ensure objects needed for visualization without any actual data dependency
* are properly evaluated. Example of this is custom shapes for bones. */
* properly evaluated. Example of this is custom shapes for bones. */
VISIBILITY,
/* **** Evaluation-Related Outer Types (with Subdata) **** */

View File

@ -2044,6 +2044,7 @@ uiLayout *UI_block_layout(uiBlock *block,
const struct uiStyle *style);
void UI_block_layout_set_current(uiBlock *block, uiLayout *layout);
void UI_block_layout_resolve(uiBlock *block, int *r_x, int *r_y);
bool UI_block_layout_needs_resolving(const uiBlock *block);
/**
* Used for property search when the layout process needs to be cancelled in order to avoid
* computing the locations for buttons, but the layout items created while adding the buttons

View File

@ -3495,8 +3495,17 @@ static void ui_textedit_begin(bContext *C, uiBut *but, uiHandleButtonData *data)
ui_but_update(but);
/* Popup blocks don't support moving after creation, so don't change the view for them. */
if (!data->searchbox) {
/* Make sure the edited button is in view. */
if (data->searchbox) {
/* Popup blocks don't support moving after creation, so don't change the view for them. */
}
else if (UI_block_layout_needs_resolving(but->block)) {
/* Layout isn't resolved yet (may happen when activating while drawing through
* #UI_but_active_only()), so can't move it into view yet. This causes
* #ui_but_update_view_for_active() to run after the layout is resolved. */
but->changed = true;
}
else {
UI_but_ensure_in_view(C, data->region, but);
}

View File

@ -5661,6 +5661,11 @@ void UI_block_layout_resolve(uiBlock *block, int *r_x, int *r_y)
}
}
bool UI_block_layout_needs_resolving(const uiBlock *block)
{
return !BLI_listbase_is_empty(&block->layouts);
}
void uiLayoutSetContextPointer(uiLayout *layout, const char *name, PointerRNA *ptr)
{
uiBlock *block = layout->root->block;

View File

@ -25,6 +25,20 @@ set(INC
../../io/alembic
../../io/collada
../../io/gpencil
../../io/wavefront_obj
../../io/usd
../../makesdna
../../makesrna
../../windowmanager
../../../../intern/guardedalloc
)
set(INC_SYS
)
set(SRC
io_alembic.c
../../io/usd
../../makesdna
../../makesrna
@ -43,6 +57,7 @@ set(SRC
io_gpencil_export.c
io_gpencil_import.c
io_gpencil_utils.c
io_obj.c
io_ops.c
io_usd.c
@ -57,6 +72,7 @@ set(SRC
set(LIB
bf_blenkernel
bf_blenlib
bf_wavefront_obj
)
if(WITH_OPENCOLLADA)

View File

@ -0,0 +1,369 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup editor/io
*/
#include "DNA_space_types.h"
#include "BKE_context.h"
#include "BKE_main.h"
#include "BKE_report.h"
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "BLI_utildefines.h"
#include "BLT_translation.h"
#include "MEM_guardedalloc.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "UI_interface.h"
#include "UI_resources.h"
#include "WM_api.h"
#include "WM_types.h"
#include "DEG_depsgraph.h"
#include "IO_wavefront_obj.h"
#include "io_obj.h"
const EnumPropertyItem io_obj_transform_axis_forward[] = {
{OBJ_AXIS_X_FORWARD, "X_FORWARD", 0, "X", "Positive X axis"},
{OBJ_AXIS_Y_FORWARD, "Y_FORWARD", 0, "Y", "Positive Y axis"},
{OBJ_AXIS_Z_FORWARD, "Z_FORWARD", 0, "Z", "Positive Z axis"},
{OBJ_AXIS_NEGATIVE_X_FORWARD, "NEGATIVE_X_FORWARD", 0, "-X", "Negative X axis"},
{OBJ_AXIS_NEGATIVE_Y_FORWARD, "NEGATIVE_Y_FORWARD", 0, "-Y", "Negative Y axis"},
{OBJ_AXIS_NEGATIVE_Z_FORWARD, "NEGATIVE_Z_FORWARD", 0, "-Z (Default)", "Negative Z axis"},
{0, NULL, 0, NULL, NULL}};
const EnumPropertyItem io_obj_transform_axis_up[] = {
{OBJ_AXIS_X_UP, "X_UP", 0, "X", "Positive X axis"},
{OBJ_AXIS_Y_UP, "Y_UP", 0, "Y (Default)", "Positive Y axis"},
{OBJ_AXIS_Z_UP, "Z_UP", 0, "Z", "Positive Z axis"},
{OBJ_AXIS_NEGATIVE_X_UP, "NEGATIVE_X_UP", 0, "-X", "Negative X axis"},
{OBJ_AXIS_NEGATIVE_Y_UP, "NEGATIVE_Y_UP", 0, "-Y", "Negative Y axis"},
{OBJ_AXIS_NEGATIVE_Z_UP, "NEGATIVE_Z_UP", 0, "-Z", "Negative Z axis"},
{0, NULL, 0, NULL, NULL}};
const EnumPropertyItem io_obj_export_evaluation_mode[] = {
{DAG_EVAL_RENDER, "DAG_EVAL_RENDER", 0, "Render", "Export objects as they appear in render"},
{DAG_EVAL_VIEWPORT,
"DAG_EVAL_VIEWPORT",
0,
"Viewport (Default)",
"Export objects as they appear in the viewport"},
{0, NULL, 0, NULL, NULL}};
static int wm_obj_export_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
{
if (!RNA_struct_property_is_set(op->ptr, "filepath")) {
Main *bmain = CTX_data_main(C);
char filepath[FILE_MAX];
if (BKE_main_blendfile_path(bmain)[0] == '\0') {
BLI_strncpy(filepath, "untitled", sizeof(filepath));
}
else {
BLI_strncpy(filepath, BKE_main_blendfile_path(bmain), sizeof(filepath));
}
BLI_path_extension_replace(filepath, sizeof(filepath), ".obj");
RNA_string_set(op->ptr, "filepath", filepath);
}
WM_event_add_fileselect(C, op);
return OPERATOR_RUNNING_MODAL;
}
static int wm_obj_export_exec(bContext *C, wmOperator *op)
{
if (!RNA_struct_property_is_set(op->ptr, "filepath")) {
BKE_report(op->reports, RPT_ERROR, "No filename given");
return OPERATOR_CANCELLED;
}
struct OBJExportParams export_params;
RNA_string_get(op->ptr, "filepath", export_params.filepath);
export_params.blen_filepath = CTX_data_main(C)->filepath;
export_params.export_animation = RNA_boolean_get(op->ptr, "export_animation");
export_params.start_frame = RNA_int_get(op->ptr, "start_frame");
export_params.end_frame = RNA_int_get(op->ptr, "end_frame");
export_params.forward_axis = RNA_enum_get(op->ptr, "forward_axis");
export_params.up_axis = RNA_enum_get(op->ptr, "up_axis");
export_params.scaling_factor = RNA_float_get(op->ptr, "scaling_factor");
export_params.export_eval_mode = RNA_enum_get(op->ptr, "export_eval_mode");
export_params.export_selected_objects = RNA_boolean_get(op->ptr, "export_selected_objects");
export_params.export_uv = RNA_boolean_get(op->ptr, "export_uv");
export_params.export_normals = RNA_boolean_get(op->ptr, "export_normals");
export_params.export_materials = RNA_boolean_get(op->ptr, "export_materials");
export_params.export_triangulated_mesh = RNA_boolean_get(op->ptr, "export_triangulated_mesh");
export_params.export_curves_as_nurbs = RNA_boolean_get(op->ptr, "export_curves_as_nurbs");
export_params.export_object_groups = RNA_boolean_get(op->ptr, "export_object_groups");
export_params.export_material_groups = RNA_boolean_get(op->ptr, "export_material_groups");
export_params.export_vertex_groups = RNA_boolean_get(op->ptr, "export_vertex_groups");
export_params.export_smooth_groups = RNA_boolean_get(op->ptr, "export_smooth_groups");
export_params.smooth_groups_bitflags = RNA_boolean_get(op->ptr, "smooth_group_bitflags");
OBJ_export(C, &export_params);
return OPERATOR_FINISHED;
}
static void ui_obj_export_settings(uiLayout *layout, PointerRNA *imfptr)
{
const bool export_animation = RNA_boolean_get(imfptr, "export_animation");
const bool export_smooth_groups = RNA_boolean_get(imfptr, "export_smooth_groups");
uiLayoutSetPropSep(layout, true);
uiLayoutSetPropDecorate(layout, false);
/* Animation options. */
uiLayout *box = uiLayoutBox(layout);
uiItemL(box, IFACE_("Animation"), ICON_ANIM);
uiLayout *col = uiLayoutColumn(box, false);
uiLayout *sub = uiLayoutColumn(col, false);
uiItemR(sub, imfptr, "export_animation", 0, NULL, ICON_NONE);
sub = uiLayoutColumn(sub, true);
uiItemR(sub, imfptr, "start_frame", 0, IFACE_("Frame Start"), ICON_NONE);
uiItemR(sub, imfptr, "end_frame", 0, IFACE_("End"), ICON_NONE);
uiLayoutSetEnabled(sub, export_animation);
/* Object Transform options. */
box = uiLayoutBox(layout);
uiItemL(box, IFACE_("Object Properties"), ICON_OBJECT_DATA);
col = uiLayoutColumn(box, false);
sub = uiLayoutColumn(col, false);
uiItemR(sub, imfptr, "forward_axis", 0, IFACE_("Axis Forward"), ICON_NONE);
uiItemR(sub, imfptr, "up_axis", 0, IFACE_("Up"), ICON_NONE);
sub = uiLayoutColumn(col, false);
uiItemR(sub, imfptr, "scaling_factor", 0, NULL, ICON_NONE);
sub = uiLayoutColumnWithHeading(col, false, IFACE_("Objects"));
uiItemR(sub, imfptr, "export_selected_objects", 0, IFACE_("Selected Only"), ICON_NONE);
uiItemR(sub, imfptr, "export_eval_mode", 0, IFACE_("Properties"), ICON_NONE);
/* Options for what to write. */
box = uiLayoutBox(layout);
uiItemL(box, IFACE_("Geometry Export"), ICON_EXPORT);
col = uiLayoutColumn(box, false);
sub = uiLayoutColumnWithHeading(col, false, IFACE_("Export"));
uiItemR(sub, imfptr, "export_uv", 0, IFACE_("UV Coordinates"), ICON_NONE);
uiItemR(sub, imfptr, "export_normals", 0, IFACE_("Normals"), ICON_NONE);
uiItemR(sub, imfptr, "export_materials", 0, IFACE_("Materials"), ICON_NONE);
uiItemR(sub, imfptr, "export_triangulated_mesh", 0, IFACE_("Triangulated Mesh"), ICON_NONE);
uiItemR(sub, imfptr, "export_curves_as_nurbs", 0, IFACE_("Curves as NURBS"), ICON_NONE);
box = uiLayoutBox(layout);
uiItemL(box, IFACE_("Grouping"), ICON_GROUP);
col = uiLayoutColumn(box, false);
sub = uiLayoutColumnWithHeading(col, false, IFACE_("Export"));
uiItemR(sub, imfptr, "export_object_groups", 0, IFACE_("Object Groups"), ICON_NONE);
uiItemR(sub, imfptr, "export_material_groups", 0, IFACE_("Material Groups"), ICON_NONE);
uiItemR(sub, imfptr, "export_vertex_groups", 0, IFACE_("Vertex Groups"), ICON_NONE);
uiItemR(sub, imfptr, "export_smooth_groups", 0, IFACE_("Smooth Groups"), ICON_NONE);
sub = uiLayoutColumn(sub, false);
uiLayoutSetEnabled(sub, export_smooth_groups);
uiItemR(sub, imfptr, "smooth_group_bitflags", 0, IFACE_("Smooth Group Bitflags"), ICON_NONE);
}
static void wm_obj_export_draw(bContext *UNUSED(C), wmOperator *op)
{
PointerRNA ptr;
RNA_pointer_create(NULL, op->type->srna, op->properties, &ptr);
ui_obj_export_settings(op->layout, &ptr);
}
/**
* Return true if any property in the UI is changed.
*/
static bool wm_obj_export_check(bContext *C, wmOperator *op)
{
char filepath[FILE_MAX];
Scene *scene = CTX_data_scene(C);
bool changed = false;
RNA_string_get(op->ptr, "filepath", filepath);
if (!BLI_path_extension_check(filepath, ".obj")) {
BLI_path_extension_ensure(filepath, FILE_MAX, ".obj");
RNA_string_set(op->ptr, "filepath", filepath);
changed = true;
}
{
int start = RNA_int_get(op->ptr, "start_frame");
int end = RNA_int_get(op->ptr, "end_frame");
/* Set the defaults. */
if (start == INT_MIN) {
start = SFRA;
changed = true;
}
if (end == INT_MAX) {
end = EFRA;
changed = true;
}
/* Fix user errors. */
if (end < start) {
end = start;
changed = true;
}
RNA_int_set(op->ptr, "start_frame", start);
RNA_int_set(op->ptr, "end_frame", end);
}
/* Both forward and up axes cannot be the same (or same except opposite sign). */
if (RNA_enum_get(op->ptr, "forward_axis") % TOTAL_AXES ==
(RNA_enum_get(op->ptr, "up_axis") % TOTAL_AXES)) {
/* TODO (ankitm) Show a warning here. */
RNA_enum_set(op->ptr, "up_axis", RNA_enum_get(op->ptr, "up_axis") % TOTAL_AXES + 1);
changed = true;
}
return changed;
}
void WM_OT_obj_export(struct wmOperatorType *ot)
{
ot->name = "Export Wavefront OBJ";
ot->description = "Save the scene to a Wavefront OBJ file";
ot->idname = "WM_OT_obj_export";
ot->invoke = wm_obj_export_invoke;
ot->exec = wm_obj_export_exec;
ot->poll = WM_operator_winactive;
ot->ui = wm_obj_export_draw;
ot->check = wm_obj_export_check;
WM_operator_properties_filesel(ot,
FILE_TYPE_FOLDER | FILE_TYPE_OBJECT_IO,
FILE_BLENDER,
FILE_SAVE,
WM_FILESEL_FILEPATH | WM_FILESEL_SHOW_PROPS,
FILE_DEFAULTDISPLAY,
FILE_SORT_ALPHA);
/* Animation options. */
RNA_def_boolean(ot->srna,
"export_animation",
false,
"Export Animation",
"Export multiple frames instead of the current frame only");
RNA_def_int(ot->srna,
"start_frame",
INT_MIN, /* wm_obj_export_check uses this to set SFRA. */
INT_MIN,
INT_MAX,
"Start Frame",
"The first frame to be exported",
INT_MIN,
INT_MAX);
RNA_def_int(ot->srna,
"end_frame",
INT_MAX, /* wm_obj_export_check uses this to set EFRA. */
INT_MIN,
INT_MAX,
"End Frame",
"The last frame to be exported",
INT_MIN,
INT_MAX);
/* Object transform options. */
RNA_def_enum(ot->srna,
"forward_axis",
io_obj_transform_axis_forward,
OBJ_AXIS_NEGATIVE_Z_FORWARD,
"Forward Axis",
"");
RNA_def_enum(ot->srna, "up_axis", io_obj_transform_axis_up, OBJ_AXIS_Y_UP, "Up Axis", "");
RNA_def_float(ot->srna,
"scaling_factor",
1.0f,
0.001f,
10000.0f,
"Scale",
"Upscale the object by this factor",
0.01,
1000.0f);
/* File Writer options. */
RNA_def_enum(ot->srna,
"export_eval_mode",
io_obj_export_evaluation_mode,
DAG_EVAL_VIEWPORT,
"Object Properties",
"Determines properties like object visibility, modifiers etc., where they differ "
"for Render and Viewport");
RNA_def_boolean(ot->srna,
"export_selected_objects",
false,
"Export Selected Objects",
"Export only selected objects instead of all supported objects");
RNA_def_boolean(ot->srna, "export_uv", true, "Export UVs", "");
RNA_def_boolean(ot->srna,
"export_normals",
true,
"Export Normals",
"Export per-face normals if the face is flat-shaded, per-face-per-loop "
"normals if smooth-shaded");
RNA_def_boolean(ot->srna,
"export_materials",
true,
"Export Materials",
"Export MTL library. There must be a Principled-BSDF node for image textures to "
"be exported to the MTL file");
RNA_def_boolean(ot->srna,
"export_triangulated_mesh",
false,
"Export Triangulated Mesh",
"All ngons with four or more vertices will be triangulated. Meshes in "
"the scene will not be affected. Behaves like Triangulate Modifier with "
"ngon-method: \"Beauty\", quad-method: \"Shortest Diagonal\", min vertices: 4");
RNA_def_boolean(ot->srna,
"export_curves_as_nurbs",
false,
"Export Curves as NURBS",
"Export curves in parametric form instead of exporting as mesh");
RNA_def_boolean(ot->srna,
"export_object_groups",
false,
"Export Object Groups",
"Append mesh name to object name, separated by a '_'");
RNA_def_boolean(ot->srna,
"export_material_groups",
false,
"Export Material Groups",
"Append mesh name and material name to object name, separated by a '_'");
RNA_def_boolean(
ot->srna,
"export_vertex_groups",
false,
"Export Vertex Groups",
"Export the name of the vertex group of a face. It is approximated "
"by choosing the vertex group with the most members among the vertices of a face");
RNA_def_boolean(
ot->srna,
"export_smooth_groups",
false,
"Export Smooth Groups",
"Every smooth-shaded face is assigned group \"1\" and every flat-shaded face \"off\"");
RNA_def_boolean(
ot->srna, "smooth_group_bitflags", false, "Generate Bitflags for Smooth Groups", "");
}

View File

@ -0,0 +1,25 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup editor/io
*/
#pragma once
struct wmOperatorType;
void WM_OT_obj_export(struct wmOperatorType *ot);

View File

@ -39,6 +39,7 @@
#include "io_cache.h"
#include "io_gpencil.h"
#include "io_obj.h"
void ED_operatortypes_io(void)
{
@ -68,4 +69,5 @@ void ED_operatortypes_io(void)
WM_operatortype_append(CACHEFILE_OT_open);
WM_operatortype_append(CACHEFILE_OT_reload);
WM_operatortype_append(WM_OT_obj_export);
}

View File

@ -1079,6 +1079,9 @@ static void graph_region_draw(const bContext *C, ARegion *region)
/* time-scrubbing */
ED_time_scrub_draw(region, scene, sc->flag & SC_SHOW_SECONDS, true);
/* current frame indicator */
ED_time_scrub_draw_current_frame(region, scene, sc->flag & SC_SHOW_SECONDS);
/* scrollers */
UI_view2d_scrollers_draw(v2d, NULL);
@ -1126,6 +1129,9 @@ static void dopesheet_region_draw(const bContext *C, ARegion *region)
/* time-scrubbing */
ED_time_scrub_draw(region, scene, sc->flag & SC_SHOW_SECONDS, true);
/* current frame indicator */
ED_time_scrub_draw_current_frame(region, scene, sc->flag & SC_SHOW_SECONDS);
/* scrollers */
UI_view2d_scrollers_draw(v2d, NULL);
}

View File

@ -275,6 +275,9 @@ static FileSelectParams *fileselect_ensure_updated_file_params(SpaceFile *sfile)
if ((prop = RNA_struct_find_property(op->ptr, "filter_usd"))) {
params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_USD : 0;
}
if ((prop = RNA_struct_find_property(op->ptr, "filter_obj"))) {
params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_OBJECT_IO : 0;
}
if ((prop = RNA_struct_find_property(op->ptr, "filter_volume"))) {
params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_VOLUME : 0;
}

View File

@ -2595,6 +2595,11 @@ static int image_new_exec(bContext *C, wmOperator *op)
else if (sima) {
ED_space_image_set(bmain, sima, ima, false);
}
else {
/* #BKE_image_add_generated creates one user by default, remove it if image is not linked to
* anything. ref. T94599. */
id_us_min(&ima->id);
}
BKE_image_signal(bmain, ima, (sima) ? &sima->iuser : NULL, IMA_SIGNAL_USER_NEW_IMAGE);

View File

@ -693,6 +693,15 @@ static void initSnappingMode(TransInfo *t)
bm_face_is_snap_target,
POINTER_FROM_UINT((BM_ELEM_SELECT | BM_ELEM_HIDDEN)));
}
else {
/* Ignore hidden geometry in the general case. */
ED_transform_snap_object_context_set_editmesh_callbacks(
t->tsnap.object_context,
(bool (*)(BMVert *, void *))BM_elem_cb_check_hflag_disabled,
(bool (*)(BMEdge *, void *))BM_elem_cb_check_hflag_disabled,
(bool (*)(BMFace *, void *))BM_elem_cb_check_hflag_disabled,
POINTER_FROM_UINT(BM_ELEM_HIDDEN));
}
}
}
else if (t->spacetype == SPACE_SEQ) {

View File

@ -287,7 +287,7 @@ class FieldConstant : public FieldNode {
const CPPType &output_cpp_type(int output_index) const override;
const CPPType &type() const;
const GPointer value() const;
GPointer value() const;
};
/**

View File

@ -684,7 +684,7 @@ const CPPType &FieldConstant::type() const
return type_;
}
const GPointer FieldConstant::value() const
GPointer FieldConstant::value() const
{
return {type_, value_};
}

View File

@ -47,7 +47,7 @@ GLShader::GLShader(const char *name) : Shader(name)
{
#if 0 /* Would be nice to have, but for now the Deferred compilation \
* does not have a GPUContext. */
BLI_assert(GLContext::get() != NULL);
BLI_assert(GLContext::get() != nullptr);
#endif
shader_program_ = glCreateProgram();
@ -58,7 +58,7 @@ GLShader::~GLShader()
{
#if 0 /* Would be nice to have, but for now the Deferred compilation \
* does not have a GPUContext. */
BLI_assert(GLContext::get() != NULL);
BLI_assert(GLContext::get() != nullptr);
#endif
/* Invalid handles are silently ignored. */
glDeleteShader(vert_shader_);

View File

@ -19,6 +19,7 @@
# ***** END GPL LICENSE BLOCK *****
add_subdirectory(common)
add_subdirectory(wavefront_obj)
if(WITH_ALEMBIC)
add_subdirectory(alembic)

View File

@ -0,0 +1,84 @@
# ***** BEGIN GPL LICENSE BLOCK *****
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# ***** END GPL LICENSE BLOCK *****
set(INC
.
./exporter
../../blenkernel
../../blenlib
../../bmesh
../../bmesh/intern
../../depsgraph
../../editors/include
../../makesdna
../../makesrna
../../nodes
../../windowmanager
../../../../intern/guardedalloc
)
set(INC_SYS
)
set(SRC
IO_wavefront_obj.cc
exporter/obj_exporter.cc
exporter/obj_export_file_writer.cc
exporter/obj_export_mesh.cc
exporter/obj_export_mtl.cc
exporter/obj_export_nurbs.cc
IO_wavefront_obj.h
exporter/obj_exporter.hh
exporter/obj_export_file_writer.hh
exporter/obj_export_io.hh
exporter/obj_export_mesh.hh
exporter/obj_export_mtl.hh
exporter/obj_export_nurbs.hh
)
set(LIB
bf_blenkernel
)
blender_add_lib(bf_wavefront_obj "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
if(WITH_GTESTS)
set(TEST_SRC
tests/obj_exporter_tests.cc
tests/obj_exporter_tests.hh
)
set(TEST_INC
${INC}
../../blenloader
../../../../tests/gtests
)
set(TEST_LIB
${LIB}
bf_blenloader_tests
bf_wavefront_obj
)
include(GTestTesting)
blender_add_test_lib(bf_wavefront_obj_tests "${TEST_SRC}" "${TEST_INC}" "${INC_SYS}" "${TEST_LIB}")
add_dependencies(bf_wavefront_obj_tests bf_wavefront_obj)
endif()

View File

@ -0,0 +1,34 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup obj
*/
#include "BLI_timeit.hh"
#include "IO_wavefront_obj.h"
#include "obj_exporter.hh"
/**
* C-interface for the exporter.
*/
void OBJ_export(bContext *C, const OBJExportParams *export_params)
{
SCOPED_TIMER("OBJ export");
blender::io::obj::exporter_main(C, *export_params);
}

View File

@ -0,0 +1,97 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup obj
*/
#pragma once
#include "BKE_context.h"
#include "BLI_path_util.h"
#include "DEG_depsgraph.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
OBJ_AXIS_X_UP = 0,
OBJ_AXIS_Y_UP = 1,
OBJ_AXIS_Z_UP = 2,
OBJ_AXIS_NEGATIVE_X_UP = 3,
OBJ_AXIS_NEGATIVE_Y_UP = 4,
OBJ_AXIS_NEGATIVE_Z_UP = 5,
} eTransformAxisUp;
typedef enum {
OBJ_AXIS_X_FORWARD = 0,
OBJ_AXIS_Y_FORWARD = 1,
OBJ_AXIS_Z_FORWARD = 2,
OBJ_AXIS_NEGATIVE_X_FORWARD = 3,
OBJ_AXIS_NEGATIVE_Y_FORWARD = 4,
OBJ_AXIS_NEGATIVE_Z_FORWARD = 5,
} eTransformAxisForward;
const int TOTAL_AXES = 3;
struct OBJExportParams {
/** Full path to the destination .OBJ file. */
char filepath[FILE_MAX];
/** Full path to current blender file (used for comments in output). */
const char *blen_filepath;
/** Whether multiple frames should be exported. */
bool export_animation;
/** The first frame to be exported. */
int start_frame;
/** The last frame to be exported. */
int end_frame;
/* Geometry Transform options. */
eTransformAxisForward forward_axis;
eTransformAxisUp up_axis;
float scaling_factor;
/* File Write Options. */
bool export_selected_objects;
eEvaluationMode export_eval_mode;
bool export_uv;
bool export_normals;
bool export_materials;
bool export_triangulated_mesh;
bool export_curves_as_nurbs;
/* Grouping options. */
bool export_object_groups;
bool export_material_groups;
bool export_vertex_groups;
/**
* Calculate smooth groups from sharp edges.
*/
bool export_smooth_groups;
/**
* Create bitflags instead of the default "0"/"1" group IDs.
*/
bool smooth_groups_bitflags;
};
void OBJ_export(bContext *C, const struct OBJExportParams *export_params);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,626 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup obj
*/
#include <algorithm>
#include <cstdio>
#include "BKE_blender_version.h"
#include "BLI_path_util.h"
#include "obj_export_mesh.hh"
#include "obj_export_mtl.hh"
#include "obj_export_nurbs.hh"
#include "obj_export_file_writer.hh"
namespace blender::io::obj {
/**
* Per reference http://www.martinreddy.net/gfx/3d/OBJ.spec:
* To turn off smoothing groups, use a value of 0 or off.
* Polygonal elements use group numbers to put elements in different smoothing groups.
* For free-form surfaces, smoothing groups are either turned on or off;
* there is no difference between values greater than 0.
*/
const int SMOOTH_GROUP_DISABLED = 0;
const int SMOOTH_GROUP_DEFAULT = 1;
const char *DEFORM_GROUP_DISABLED = "off";
/* There is no deform group default name. Use what the user set in the UI. */
/**
* Per reference http://www.martinreddy.net/gfx/3d/OBJ.spec:
* Once a material is assigned, it cannot be turned off; it can only be changed.
* If a material name is not specified, a white material is used.
* So an empty material name is written. */
const char *MATERIAL_GROUP_DISABLED = "";
/**
* Write one line of polygon indices as "f v1/vt1/vn1 v2/vt2/vn2 ...".
*/
void OBJWriter::write_vert_uv_normal_indices(Span<int> vert_indices,
Span<int> uv_indices,
Span<int> normal_indices) const
{
BLI_assert(vert_indices.size() == uv_indices.size() &&
vert_indices.size() == normal_indices.size());
file_handler_->write<eOBJSyntaxElement::poly_element_begin>();
for (int j = 0; j < vert_indices.size(); j++) {
file_handler_->write<eOBJSyntaxElement::vertex_uv_normal_indices>(
vert_indices[j] + index_offsets_.vertex_offset + 1,
uv_indices[j] + index_offsets_.uv_vertex_offset + 1,
normal_indices[j] + index_offsets_.normal_offset + 1);
}
file_handler_->write<eOBJSyntaxElement::poly_element_end>();
}
/**
* Write one line of polygon indices as "f v1//vn1 v2//vn2 ...".
*/
void OBJWriter::write_vert_normal_indices(Span<int> vert_indices,
Span<int> /*uv_indices*/,
Span<int> normal_indices) const
{
BLI_assert(vert_indices.size() == normal_indices.size());
file_handler_->write<eOBJSyntaxElement::poly_element_begin>();
for (int j = 0; j < vert_indices.size(); j++) {
file_handler_->write<eOBJSyntaxElement::vertex_normal_indices>(
vert_indices[j] + index_offsets_.vertex_offset + 1,
normal_indices[j] + index_offsets_.normal_offset + 1);
}
file_handler_->write<eOBJSyntaxElement::poly_element_end>();
}
/**
* Write one line of polygon indices as "f v1/vt1 v2/vt2 ...".
*/
void OBJWriter::write_vert_uv_indices(Span<int> vert_indices,
Span<int> uv_indices,
Span<int> /*normal_indices*/) const
{
BLI_assert(vert_indices.size() == uv_indices.size());
file_handler_->write<eOBJSyntaxElement::poly_element_begin>();
for (int j = 0; j < vert_indices.size(); j++) {
file_handler_->write<eOBJSyntaxElement::vertex_uv_indices>(
vert_indices[j] + index_offsets_.vertex_offset + 1,
uv_indices[j] + index_offsets_.uv_vertex_offset + 1);
}
file_handler_->write<eOBJSyntaxElement::poly_element_end>();
}
/**
* Write one line of polygon indices as "f v1 v2 ...".
*/
void OBJWriter::write_vert_indices(Span<int> vert_indices,
Span<int> /*uv_indices*/,
Span<int> /*normal_indices*/) const
{
file_handler_->write<eOBJSyntaxElement::poly_element_begin>();
for (const int vert_index : vert_indices) {
file_handler_->write<eOBJSyntaxElement::vertex_indices>(vert_index +
index_offsets_.vertex_offset + 1);
}
file_handler_->write<eOBJSyntaxElement::poly_element_end>();
}
void OBJWriter::write_header() const
{
using namespace std::string_literals;
file_handler_->write<eOBJSyntaxElement::string>("# Blender "s + BKE_blender_version_string() +
"\n");
file_handler_->write<eOBJSyntaxElement::string>("# www.blender.org\n");
}
/**
* Write file name of Material Library in .OBJ file.
*/
void OBJWriter::write_mtllib_name(const StringRefNull mtl_filepath) const
{
/* Split .MTL file path into parent directory and filename. */
char mtl_file_name[FILE_MAXFILE];
char mtl_dir_name[FILE_MAXDIR];
BLI_split_dirfile(mtl_filepath.data(), mtl_dir_name, mtl_file_name, FILE_MAXDIR, FILE_MAXFILE);
file_handler_->write<eOBJSyntaxElement::mtllib>(mtl_file_name);
}
/**
* Write an object's group with mesh and/or material name appended conditionally.
*/
void OBJWriter::write_object_group(const OBJMesh &obj_mesh_data) const
{
/* "o object_name" is not mandatory. A valid .OBJ file may contain neither
* "o name" nor "g group_name". */
BLI_assert(export_params_.export_object_groups);
if (!export_params_.export_object_groups) {
return;
}
const std::string object_name = obj_mesh_data.get_object_name();
const char *object_mesh_name = obj_mesh_data.get_object_mesh_name();
const char *object_material_name = obj_mesh_data.get_object_material_name(0);
if (export_params_.export_materials && export_params_.export_material_groups &&
object_material_name) {
file_handler_->write<eOBJSyntaxElement::object_group>(object_name + "_" + object_mesh_name +
"_" + object_material_name);
return;
}
file_handler_->write<eOBJSyntaxElement::object_group>(object_name + "_" + object_mesh_name);
}
/**
* Write object's name or group.
*/
void OBJWriter::write_object_name(const OBJMesh &obj_mesh_data) const
{
const char *object_name = obj_mesh_data.get_object_name();
if (export_params_.export_object_groups) {
write_object_group(obj_mesh_data);
return;
}
file_handler_->write<eOBJSyntaxElement::object_name>(object_name);
}
/**
* Write vertex coordinates for all vertices as "v x y z".
*/
void OBJWriter::write_vertex_coords(const OBJMesh &obj_mesh_data) const
{
const int tot_vertices = obj_mesh_data.tot_vertices();
for (int i = 0; i < tot_vertices; i++) {
float3 vertex = obj_mesh_data.calc_vertex_coords(i, export_params_.scaling_factor);
file_handler_->write<eOBJSyntaxElement::vertex_coords>(vertex[0], vertex[1], vertex[2]);
}
}
/**
* Write UV vertex coordinates for all vertices as "vt u v".
* \note UV indices are stored here, but written later.
*/
void OBJWriter::write_uv_coords(OBJMesh &r_obj_mesh_data) const
{
Vector<std::array<float, 2>> uv_coords;
/* UV indices are calculated and stored in an OBJMesh member here. */
r_obj_mesh_data.store_uv_coords_and_indices(uv_coords);
for (const std::array<float, 2> &uv_vertex : uv_coords) {
file_handler_->write<eOBJSyntaxElement::uv_vertex_coords>(uv_vertex[0], uv_vertex[1]);
}
}
/**
* Write loop normals for smooth-shaded polygons, and polygon normals otherwise, as "vn x y z".
*/
void OBJWriter::write_poly_normals(const OBJMesh &obj_mesh_data) const
{
obj_mesh_data.ensure_mesh_normals();
Vector<float3> lnormals;
const int tot_polygons = obj_mesh_data.tot_polygons();
for (int i = 0; i < tot_polygons; i++) {
if (obj_mesh_data.is_ith_poly_smooth(i)) {
obj_mesh_data.calc_loop_normals(i, lnormals);
for (const float3 &lnormal : lnormals) {
file_handler_->write<eOBJSyntaxElement::normal>(lnormal[0], lnormal[1], lnormal[2]);
}
}
else {
float3 poly_normal = obj_mesh_data.calc_poly_normal(i);
file_handler_->write<eOBJSyntaxElement::normal>(
poly_normal[0], poly_normal[1], poly_normal[2]);
}
}
}
/**
* Write smooth group if polygon at the given index is shaded smooth else "s 0"
*/
int OBJWriter::write_smooth_group(const OBJMesh &obj_mesh_data,
const int poly_index,
const int last_poly_smooth_group) const
{
int current_group = SMOOTH_GROUP_DISABLED;
if (!export_params_.export_smooth_groups && obj_mesh_data.is_ith_poly_smooth(poly_index)) {
/* Smooth group calculation is disabled, but polygon is smooth-shaded. */
current_group = SMOOTH_GROUP_DEFAULT;
}
else if (obj_mesh_data.is_ith_poly_smooth(poly_index)) {
/* Smooth group calc is enabled and polygon is smoothshaded, so find the group. */
current_group = obj_mesh_data.ith_smooth_group(poly_index);
}
if (current_group == last_poly_smooth_group) {
/* Group has already been written, even if it is "s 0". */
return current_group;
}
file_handler_->write<eOBJSyntaxElement::smooth_group>(current_group);
return current_group;
}
/**
* Write material name and material group of a polygon in the .OBJ file.
* \return #mat_nr of the polygon at the given index.
* \note It doesn't write to the material library.
*/
int16_t OBJWriter::write_poly_material(const OBJMesh &obj_mesh_data,
const int poly_index,
const int16_t last_poly_mat_nr,
std::function<const char *(int)> matname_fn) const
{
if (!export_params_.export_materials || obj_mesh_data.tot_materials() <= 0) {
return last_poly_mat_nr;
}
const int16_t current_mat_nr = obj_mesh_data.ith_poly_matnr(poly_index);
/* Whenever a polygon with a new material is encountered, write its material
* and/or group, otherwise pass. */
if (last_poly_mat_nr == current_mat_nr) {
return current_mat_nr;
}
if (current_mat_nr == NOT_FOUND) {
file_handler_->write<eOBJSyntaxElement::poly_usemtl>(MATERIAL_GROUP_DISABLED);
return current_mat_nr;
}
if (export_params_.export_object_groups) {
write_object_group(obj_mesh_data);
}
const char *mat_name = matname_fn(current_mat_nr);
if (!mat_name) {
mat_name = MATERIAL_GROUP_DISABLED;
}
file_handler_->write<eOBJSyntaxElement::poly_usemtl>(mat_name);
return current_mat_nr;
}
/**
* Write the name of the deform group of a polygon.
*/
int16_t OBJWriter::write_vertex_group(const OBJMesh &obj_mesh_data,
const int poly_index,
const int16_t last_poly_vertex_group) const
{
if (!export_params_.export_vertex_groups) {
return last_poly_vertex_group;
}
const int16_t current_group = obj_mesh_data.get_poly_deform_group_index(poly_index);
if (current_group == last_poly_vertex_group) {
/* No vertex group found in this polygon, just like in the last iteration. */
return current_group;
}
if (current_group == NOT_FOUND) {
file_handler_->write<eOBJSyntaxElement::object_group>(DEFORM_GROUP_DISABLED);
return current_group;
}
file_handler_->write<eOBJSyntaxElement::object_group>(
obj_mesh_data.get_poly_deform_group_name(current_group));
return current_group;
}
/**
* \return Writer function with appropriate polygon-element syntax.
*/
OBJWriter::func_vert_uv_normal_indices OBJWriter::get_poly_element_writer(
const int total_uv_vertices) const
{
if (export_params_.export_normals) {
if (export_params_.export_uv && (total_uv_vertices > 0)) {
/* Write both normals and UV indices. */
return &OBJWriter::write_vert_uv_normal_indices;
}
/* Write normals indices. */
return &OBJWriter::write_vert_normal_indices;
}
/* Write UV indices. */
if (export_params_.export_uv && (total_uv_vertices > 0)) {
return &OBJWriter::write_vert_uv_indices;
}
/* Write neither normals nor UV indices. */
return &OBJWriter::write_vert_indices;
}
/**
* Write polygon elements with at least vertex indices, and conditionally with UV vertex
* indices and polygon normal indices. Also write groups: smooth, vertex, material.
* The matname_fn turns a 0-indexed material slot number in an Object into the
* name used in the .obj file.
* \note UV indices were stored while writing UV vertices.
*/
void OBJWriter::write_poly_elements(const OBJMesh &obj_mesh_data,
std::function<const char *(int)> matname_fn)
{
int last_poly_smooth_group = NEGATIVE_INIT;
int16_t last_poly_vertex_group = NEGATIVE_INIT;
int16_t last_poly_mat_nr = NEGATIVE_INIT;
const func_vert_uv_normal_indices poly_element_writer = get_poly_element_writer(
obj_mesh_data.tot_uv_vertices());
/* Number of normals may not be equal to number of polygons due to smooth shading. */
int per_object_tot_normals = 0;
const int tot_polygons = obj_mesh_data.tot_polygons();
for (int i = 0; i < tot_polygons; i++) {
Vector<int> poly_vertex_indices = obj_mesh_data.calc_poly_vertex_indices(i);
Span<int> poly_uv_indices = obj_mesh_data.calc_poly_uv_indices(i);
/* For an Object, a normal index depends on how many of its normals have been written before
* it. This is unknown because of smooth shading. So pass "per object total normals"
* and update it after each call. */
int new_normals = 0;
Vector<int> poly_normal_indices;
std::tie(new_normals, poly_normal_indices) = obj_mesh_data.calc_poly_normal_indices(
i, per_object_tot_normals);
per_object_tot_normals += new_normals;
last_poly_smooth_group = write_smooth_group(obj_mesh_data, i, last_poly_smooth_group);
last_poly_vertex_group = write_vertex_group(obj_mesh_data, i, last_poly_vertex_group);
last_poly_mat_nr = write_poly_material(obj_mesh_data, i, last_poly_mat_nr, matname_fn);
(this->*poly_element_writer)(poly_vertex_indices, poly_uv_indices, poly_normal_indices);
}
/* Unusual: Other indices are updated in #OBJWriter::update_index_offsets. */
index_offsets_.normal_offset += per_object_tot_normals;
}
/**
* Write loose edges of a mesh as "l v1 v2".
*/
void OBJWriter::write_edges_indices(const OBJMesh &obj_mesh_data) const
{
obj_mesh_data.ensure_mesh_edges();
const int tot_edges = obj_mesh_data.tot_edges();
for (int edge_index = 0; edge_index < tot_edges; edge_index++) {
const std::optional<std::array<int, 2>> vertex_indices =
obj_mesh_data.calc_loose_edge_vert_indices(edge_index);
if (!vertex_indices) {
continue;
}
file_handler_->write<eOBJSyntaxElement::edge>(
(*vertex_indices)[0] + index_offsets_.vertex_offset + 1,
(*vertex_indices)[1] + index_offsets_.vertex_offset + 1);
}
}
/**
* Write a NURBS curve to the .OBJ file in parameter form.
*/
void OBJWriter::write_nurbs_curve(const OBJCurve &obj_nurbs_data) const
{
const int total_splines = obj_nurbs_data.total_splines();
for (int spline_idx = 0; spline_idx < total_splines; spline_idx++) {
const int total_vertices = obj_nurbs_data.total_spline_vertices(spline_idx);
for (int vertex_idx = 0; vertex_idx < total_vertices; vertex_idx++) {
const float3 vertex_coords = obj_nurbs_data.vertex_coordinates(
spline_idx, vertex_idx, export_params_.scaling_factor);
file_handler_->write<eOBJSyntaxElement::vertex_coords>(
vertex_coords[0], vertex_coords[1], vertex_coords[2]);
}
const char *nurbs_name = obj_nurbs_data.get_curve_name();
const int nurbs_degree = obj_nurbs_data.get_nurbs_degree(spline_idx);
file_handler_->write<eOBJSyntaxElement::object_group>(nurbs_name);
file_handler_->write<eOBJSyntaxElement::cstype>();
file_handler_->write<eOBJSyntaxElement::nurbs_degree>(nurbs_degree);
/**
* The numbers written here are indices into the vertex coordinates written
* earlier, relative to the line that is going to be written.
* [0.0 - 1.0] is the curve parameter range.
* 0.0 1.0 -1 -2 -3 -4 for a non-cyclic curve with 4 vertices.
* 0.0 1.0 -1 -2 -3 -4 -1 -2 -3 for a cyclic curve with 4 vertices.
*/
const int total_control_points = obj_nurbs_data.total_spline_control_points(spline_idx);
file_handler_->write<eOBJSyntaxElement::curve_element_begin>();
for (int i = 0; i < total_control_points; i++) {
/* "+1" to keep indices one-based, even if they're negative: i.e., -1 refers to the
* last vertex coordinate, -2 second last. */
file_handler_->write<eOBJSyntaxElement::vertex_indices>(-((i % total_vertices) + 1));
}
file_handler_->write<eOBJSyntaxElement::curve_element_end>();
/**
* In "parm u 0 0.1 .." line:, (total control points + 2) equidistant numbers in the
* parameter range are inserted.
*/
file_handler_->write<eOBJSyntaxElement::nurbs_parameter_begin>();
for (int i = 1; i <= total_control_points + 2; i++) {
file_handler_->write<eOBJSyntaxElement::nurbs_parameters>(1.0f * i /
(total_control_points + 2 + 1));
}
file_handler_->write<eOBJSyntaxElement::nurbs_parameter_end>();
file_handler_->write<eOBJSyntaxElement::nurbs_group_end>();
}
}
/**
* When there are multiple objects in a frame, the indices of previous objects' coordinates or
* normals add up.
*/
void OBJWriter::update_index_offsets(const OBJMesh &obj_mesh_data)
{
index_offsets_.vertex_offset += obj_mesh_data.tot_vertices();
index_offsets_.uv_vertex_offset += obj_mesh_data.tot_uv_vertices();
/* Normal index is updated right after writing the normals. */
}
/* -------------------------------------------------------------------- */
/** \name .MTL writers.
* \{ */
/**
* Convert #float3 to string of space-separated numbers, with no leading or trailing space.
* Only to be used in NON-performance-critical code.
*/
static std::string float3_to_string(const float3 &numbers)
{
std::ostringstream r_string;
r_string << numbers[0] << " " << numbers[1] << " " << numbers[2];
return r_string.str();
};
/*
* Create the .MTL file.
*/
MTLWriter::MTLWriter(const char *obj_filepath) noexcept(false)
{
mtl_filepath_ = obj_filepath;
const bool ok = BLI_path_extension_replace(mtl_filepath_.data(), FILE_MAX, ".mtl");
if (!ok) {
throw std::system_error(ENAMETOOLONG, std::system_category(), "");
}
file_handler_ = std::make_unique<FileHandler<eFileType::MTL>>(mtl_filepath_);
}
void MTLWriter::write_header(const char *blen_filepath) const
{
using namespace std::string_literals;
const char *blen_basename = (blen_filepath && blen_filepath[0] != '\0') ?
BLI_path_basename(blen_filepath) :
"None";
file_handler_->write<eMTLSyntaxElement::string>("# Blender "s + BKE_blender_version_string() +
" MTL File: '" + blen_basename + "'\n");
file_handler_->write<eMTLSyntaxElement::string>("# www.blender.org\n");
}
StringRefNull MTLWriter::mtl_file_path() const
{
return mtl_filepath_;
}
/**
* Write properties sourced from p-BSDF node or #Object.Material.
*/
void MTLWriter::write_bsdf_properties(const MTLMaterial &mtl_material)
{
file_handler_->write<eMTLSyntaxElement::Ns>(mtl_material.Ns);
file_handler_->write<eMTLSyntaxElement::Ka>(
mtl_material.Ka.x, mtl_material.Ka.y, mtl_material.Ka.z);
file_handler_->write<eMTLSyntaxElement::Kd>(
mtl_material.Kd.x, mtl_material.Kd.y, mtl_material.Kd.z);
file_handler_->write<eMTLSyntaxElement::Ks>(
mtl_material.Ks.x, mtl_material.Ks.y, mtl_material.Ks.z);
file_handler_->write<eMTLSyntaxElement::Ke>(
mtl_material.Ke.x, mtl_material.Ke.y, mtl_material.Ke.z);
file_handler_->write<eMTLSyntaxElement::Ni>(mtl_material.Ni);
file_handler_->write<eMTLSyntaxElement::d>(mtl_material.d);
file_handler_->write<eMTLSyntaxElement::illum>(mtl_material.illum);
}
/**
* Write a texture map in the form "map_XX -s 1. 1. 1. -o 0. 0. 0. [-bm 1.] path/to/image".
*/
void MTLWriter::write_texture_map(
const MTLMaterial &mtl_material,
const Map<const eMTLSyntaxElement, tex_map_XX>::Item &texture_map)
{
std::string translation;
std::string scale;
std::string map_bump_strength;
/* Optional strings should have their own leading spaces. */
if (texture_map.value.translation != float3{0.0f, 0.0f, 0.0f}) {
translation.append(" -s ").append(float3_to_string(texture_map.value.translation));
}
if (texture_map.value.scale != float3{1.0f, 1.0f, 1.0f}) {
scale.append(" -o ").append(float3_to_string(texture_map.value.scale));
}
if (texture_map.key == eMTLSyntaxElement::map_Bump && mtl_material.map_Bump_strength > 0.0001f) {
map_bump_strength.append(" -bm ").append(std::to_string(mtl_material.map_Bump_strength));
}
#define SYNTAX_DISPATCH(eMTLSyntaxElement) \
if (texture_map.key == eMTLSyntaxElement) { \
file_handler_->write<eMTLSyntaxElement>(translation + scale + map_bump_strength, \
texture_map.value.image_path); \
return; \
}
SYNTAX_DISPATCH(eMTLSyntaxElement::map_Kd);
SYNTAX_DISPATCH(eMTLSyntaxElement::map_Ks);
SYNTAX_DISPATCH(eMTLSyntaxElement::map_Ns);
SYNTAX_DISPATCH(eMTLSyntaxElement::map_d);
SYNTAX_DISPATCH(eMTLSyntaxElement::map_refl);
SYNTAX_DISPATCH(eMTLSyntaxElement::map_Ke);
SYNTAX_DISPATCH(eMTLSyntaxElement::map_Bump);
BLI_assert(!"This map type was not written to the file.");
}
/**
* Write all of the material specifications to the MTL file.
* For consistency of output from run to run (useful for testing),
* the materials are sorted by name before writing.
*/
void MTLWriter::write_materials()
{
if (mtlmaterials_.size() == 0) {
return;
}
std::sort(mtlmaterials_.begin(),
mtlmaterials_.end(),
[](const MTLMaterial &a, const MTLMaterial &b) { return a.name < b.name; });
for (const MTLMaterial &mtlmat : mtlmaterials_) {
file_handler_->write<eMTLSyntaxElement::string>("\n");
file_handler_->write<eMTLSyntaxElement::newmtl>(mtlmat.name);
write_bsdf_properties(mtlmat);
for (const Map<const eMTLSyntaxElement, tex_map_XX>::Item &texture_map :
mtlmat.texture_maps.items()) {
if (!texture_map.value.image_path.empty()) {
write_texture_map(mtlmat, texture_map);
}
}
}
}
/**
* Add the materials of the given object to MTLWriter, deduping
* against ones that are already there.
* Return a Vector of indices into mtlmaterials_ that hold the MTLMaterial
* that corresponds to each material slot, in order, of the given Object.
* Indexes are returned rather than pointers to the MTLMaterials themselves
* because the mtlmaterials_ Vector may move around when resized.
*/
Vector<int> MTLWriter::add_materials(const OBJMesh &mesh_to_export)
{
Vector<int> r_mtl_indices;
r_mtl_indices.resize(mesh_to_export.tot_materials());
for (int16_t i = 0; i < mesh_to_export.tot_materials(); i++) {
const Material *material = mesh_to_export.get_object_material(i);
if (!material) {
r_mtl_indices[i] = -1;
continue;
}
int mtlmat_index = material_map_.lookup_default(material, -1);
if (mtlmat_index != -1) {
r_mtl_indices[i] = mtlmat_index;
}
else {
mtlmaterials_.append(mtlmaterial_for_material(material));
r_mtl_indices[i] = mtlmaterials_.size() - 1;
material_map_.add_new(material, r_mtl_indices[i]);
}
}
return r_mtl_indices;
}
const char *MTLWriter::mtlmaterial_name(int index)
{
if (index < 0 || index >= mtlmaterials_.size()) {
return nullptr;
}
return mtlmaterials_[index].name.c_str();
}
/** \} */
} // namespace blender::io::obj

View File

@ -0,0 +1,132 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup obj
*/
#pragma once
#include "DNA_meshdata_types.h"
#include "BLI_map.hh"
#include "BLI_vector.hh"
#include "IO_wavefront_obj.h"
#include "obj_export_io.hh"
#include "obj_export_mtl.hh"
namespace blender::io::obj {
class OBJCurve;
class OBJMesh;
/**
* Total vertices/ UV vertices/ normals of previous Objects
* should be added to the current Object's indices.
*/
struct IndexOffsets {
int vertex_offset;
int uv_vertex_offset;
int normal_offset;
};
/**
* Responsible for writing a .OBJ file.
*/
class OBJWriter : NonMovable, NonCopyable {
private:
const OBJExportParams &export_params_;
std::unique_ptr<FileHandler<eFileType::OBJ>> file_handler_ = nullptr;
IndexOffsets index_offsets_{0, 0, 0};
public:
OBJWriter(const char *filepath, const OBJExportParams &export_params) noexcept(false)
: export_params_(export_params)
{
file_handler_ = std::make_unique<FileHandler<eFileType::OBJ>>(filepath);
}
void write_header() const;
void write_object_name(const OBJMesh &obj_mesh_data) const;
void write_object_group(const OBJMesh &obj_mesh_data) const;
void write_mtllib_name(const StringRefNull mtl_filepath) const;
void write_vertex_coords(const OBJMesh &obj_mesh_data) const;
void write_uv_coords(OBJMesh &obj_mesh_data) const;
void write_poly_normals(const OBJMesh &obj_mesh_data) const;
int write_smooth_group(const OBJMesh &obj_mesh_data,
int poly_index,
const int last_poly_smooth_group) const;
int16_t write_poly_material(const OBJMesh &obj_mesh_data,
const int poly_index,
const int16_t last_poly_mat_nr,
std::function<const char *(int)> matname_fn) const;
int16_t write_vertex_group(const OBJMesh &obj_mesh_data,
const int poly_index,
const int16_t last_poly_vertex_group) const;
void write_poly_elements(const OBJMesh &obj_mesh_data,
std::function<const char *(int)> matname_fn);
void write_edges_indices(const OBJMesh &obj_mesh_data) const;
void write_nurbs_curve(const OBJCurve &obj_nurbs_data) const;
void update_index_offsets(const OBJMesh &obj_mesh_data);
private:
using func_vert_uv_normal_indices = void (OBJWriter::*)(Span<int> vert_indices,
Span<int> uv_indices,
Span<int> normal_indices) const;
func_vert_uv_normal_indices get_poly_element_writer(const int total_uv_vertices) const;
void write_vert_uv_normal_indices(Span<int> vert_indices,
Span<int> uv_indices,
Span<int> normal_indices) const;
void write_vert_normal_indices(Span<int> vert_indices,
Span<int> /*uv_indices*/,
Span<int> normal_indices) const;
void write_vert_uv_indices(Span<int> vert_indices,
Span<int> uv_indices,
Span<int> /*normal_indices*/) const;
void write_vert_indices(Span<int> vert_indices,
Span<int> /*uv_indices*/,
Span<int> /*normal_indices*/) const;
};
/**
* Responsible for writing a .MTL file.
*/
class MTLWriter : NonMovable, NonCopyable {
private:
std::unique_ptr<FileHandler<eFileType::MTL>> file_handler_ = nullptr;
std::string mtl_filepath_;
Vector<MTLMaterial> mtlmaterials_;
/* Map from a Material* to an index into mtlmaterials_. */
Map<const Material *, int> material_map_;
public:
MTLWriter(const char *obj_filepath) noexcept(false);
void write_header(const char *blen_filepath) const;
void write_materials();
StringRefNull mtl_file_path() const;
Vector<int> add_materials(const OBJMesh &mesh_to_export);
const char *mtlmaterial_name(int index);
private:
void write_bsdf_properties(const MTLMaterial &mtl_material);
void write_texture_map(const MTLMaterial &mtl_material,
const Map<const eMTLSyntaxElement, tex_map_XX>::Item &texture_map);
};
} // namespace blender::io::obj

View File

@ -0,0 +1,340 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup obj
*/
#pragma once
#include <cstdio>
#include <string>
#include <system_error>
#include <type_traits>
#include "BLI_compiler_attrs.h"
#include "BLI_string_ref.hh"
#include "BLI_utility_mixins.hh"
namespace blender::io::obj {
enum class eFileType {
OBJ,
MTL,
};
enum class eOBJSyntaxElement {
vertex_coords,
uv_vertex_coords,
normal,
poly_element_begin,
vertex_uv_normal_indices,
vertex_normal_indices,
vertex_uv_indices,
vertex_indices,
poly_element_end,
poly_usemtl,
edge,
cstype,
nurbs_degree,
curve_element_begin,
curve_element_end,
nurbs_parameter_begin,
nurbs_parameters,
nurbs_parameter_end,
nurbs_group_end,
new_line,
mtllib,
smooth_group,
object_group,
object_name,
/* Use rarely. New line is NOT included for string. */
string,
};
enum class eMTLSyntaxElement {
newmtl,
Ni,
d,
Ns,
illum,
Ka,
Kd,
Ks,
Ke,
map_Kd,
map_Ks,
map_Ns,
map_d,
map_refl,
map_Ke,
map_Bump,
/* Use rarely. New line is NOT included for string. */
string,
};
template<eFileType filetype> struct FileTypeTraits;
template<> struct FileTypeTraits<eFileType::OBJ> {
using SyntaxType = eOBJSyntaxElement;
};
template<> struct FileTypeTraits<eFileType::MTL> {
using SyntaxType = eMTLSyntaxElement;
};
template<eFileType type> struct Formatting {
const char *fmt = nullptr;
const int total_args = 0;
/* Fail to compile by default. */
const bool is_type_valid = false;
};
/**
* Type dependent but always false. Use to add a conditional compile-time error.
*/
template<typename T> struct always_false : std::false_type {
};
template<typename... T>
constexpr bool is_type_float = (... && std::is_floating_point_v<std::decay_t<T>>);
template<typename... T>
constexpr bool is_type_integral = (... && std::is_integral_v<std::decay_t<T>>);
template<typename... T>
constexpr bool is_type_string_related = (... && std::is_constructible_v<std::string, T>);
template<eFileType filetype, typename... T>
constexpr std::enable_if_t<filetype == eFileType::OBJ, Formatting<filetype>>
syntax_elem_to_formatting(const eOBJSyntaxElement key)
{
switch (key) {
case eOBJSyntaxElement::vertex_coords: {
return {"v %f %f %f\n", 3, is_type_float<T...>};
}
case eOBJSyntaxElement::uv_vertex_coords: {
return {"vt %f %f\n", 2, is_type_float<T...>};
}
case eOBJSyntaxElement::normal: {
return {"vn %f %f %f\n", 3, is_type_float<T...>};
}
case eOBJSyntaxElement::poly_element_begin: {
return {"f", 0, is_type_string_related<T...>};
}
case eOBJSyntaxElement::vertex_uv_normal_indices: {
return {" %d/%d/%d", 3, is_type_integral<T...>};
}
case eOBJSyntaxElement::vertex_normal_indices: {
return {" %d//%d", 2, is_type_integral<T...>};
}
case eOBJSyntaxElement::vertex_uv_indices: {
return {" %d/%d", 2, is_type_integral<T...>};
}
case eOBJSyntaxElement::vertex_indices: {
return {" %d", 1, is_type_integral<T...>};
}
case eOBJSyntaxElement::poly_usemtl: {
return {"usemtl %s\n", 1, is_type_string_related<T...>};
}
case eOBJSyntaxElement::edge: {
return {"l %d %d\n", 2, is_type_integral<T...>};
}
case eOBJSyntaxElement::cstype: {
return {"cstype bspline\n", 0, is_type_string_related<T...>};
}
case eOBJSyntaxElement::nurbs_degree: {
return {"deg %d\n", 1, is_type_integral<T...>};
}
case eOBJSyntaxElement::curve_element_begin: {
return {"curv 0.0 1.0", 0, is_type_string_related<T...>};
}
case eOBJSyntaxElement::nurbs_parameter_begin: {
return {"parm 0.0", 0, is_type_string_related<T...>};
}
case eOBJSyntaxElement::nurbs_parameters: {
return {" %f", 1, is_type_float<T...>};
}
case eOBJSyntaxElement::nurbs_parameter_end: {
return {" 1.0\n", 0, is_type_string_related<T...>};
}
case eOBJSyntaxElement::nurbs_group_end: {
return {"end\n", 0, is_type_string_related<T...>};
}
case eOBJSyntaxElement::poly_element_end: {
ATTR_FALLTHROUGH;
}
case eOBJSyntaxElement::curve_element_end: {
ATTR_FALLTHROUGH;
}
case eOBJSyntaxElement::new_line: {
return {"\n", 0, is_type_string_related<T...>};
}
case eOBJSyntaxElement::mtllib: {
return {"mtllib %s\n", 1, is_type_string_related<T...>};
}
case eOBJSyntaxElement::smooth_group: {
return {"s %d\n", 1, is_type_integral<T...>};
}
case eOBJSyntaxElement::object_group: {
return {"g %s\n", 1, is_type_string_related<T...>};
}
case eOBJSyntaxElement::object_name: {
return {"o %s\n", 1, is_type_string_related<T...>};
}
case eOBJSyntaxElement::string: {
return {"%s", 1, is_type_string_related<T...>};
}
}
}
template<eFileType filetype, typename... T>
constexpr std::enable_if_t<filetype == eFileType::MTL, Formatting<filetype>>
syntax_elem_to_formatting(const eMTLSyntaxElement key)
{
switch (key) {
case eMTLSyntaxElement::newmtl: {
return {"newmtl %s\n", 1, is_type_string_related<T...>};
}
case eMTLSyntaxElement::Ni: {
return {"Ni %.6f\n", 1, is_type_float<T...>};
}
case eMTLSyntaxElement::d: {
return {"d %.6f\n", 1, is_type_float<T...>};
}
case eMTLSyntaxElement::Ns: {
return {"Ns %.6f\n", 1, is_type_float<T...>};
}
case eMTLSyntaxElement::illum: {
return {"illum %d\n", 1, is_type_integral<T...>};
}
case eMTLSyntaxElement::Ka: {
return {"Ka %.6f %.6f %.6f\n", 3, is_type_float<T...>};
}
case eMTLSyntaxElement::Kd: {
return {"Kd %.6f %.6f %.6f\n", 3, is_type_float<T...>};
}
case eMTLSyntaxElement::Ks: {
return {"Ks %.6f %.6f %.6f\n", 3, is_type_float<T...>};
}
case eMTLSyntaxElement::Ke: {
return {"Ke %.6f %.6f %.6f\n", 3, is_type_float<T...>};
}
/* Keep only one space between options since filepaths may have leading spaces too. */
case eMTLSyntaxElement::map_Kd: {
return {"map_Kd %s %s\n", 2, is_type_string_related<T...>};
}
case eMTLSyntaxElement::map_Ks: {
return {"map_Ks %s %s\n", 2, is_type_string_related<T...>};
}
case eMTLSyntaxElement::map_Ns: {
return {"map_Ns %s %s\n", 2, is_type_string_related<T...>};
}
case eMTLSyntaxElement::map_d: {
return {"map_d %s %s\n", 2, is_type_string_related<T...>};
}
case eMTLSyntaxElement::map_refl: {
return {"map_refl %s %s\n", 2, is_type_string_related<T...>};
}
case eMTLSyntaxElement::map_Ke: {
return {"map_Ke %s %s\n", 2, is_type_string_related<T...>};
}
case eMTLSyntaxElement::map_Bump: {
return {"map_Bump %s %s\n", 2, is_type_string_related<T...>};
}
case eMTLSyntaxElement::string: {
return {"%s", 1, is_type_string_related<T...>};
}
}
}
template<eFileType filetype> class FileHandler : NonCopyable, NonMovable {
private:
FILE *outfile_ = nullptr;
std::string outfile_path_;
public:
FileHandler(std::string outfile_path) noexcept(false) : outfile_path_(std::move(outfile_path))
{
outfile_ = std::fopen(outfile_path_.c_str(), "w");
if (!outfile_) {
throw std::system_error(errno, std::system_category(), "Cannot open file");
}
}
~FileHandler()
{
if (outfile_ && std::fclose(outfile_)) {
std::cerr << "Error: could not close the file '" << outfile_path_
<< "' properly, it may be corrupted." << std::endl;
}
}
template<typename FileTypeTraits<filetype>::SyntaxType key, typename... T>
constexpr void write(T &&...args) const
{
constexpr Formatting<filetype> fmt_nargs_valid = syntax_elem_to_formatting<filetype, T...>(
key);
write__impl<fmt_nargs_valid.total_args>(fmt_nargs_valid.fmt, std::forward<T>(args)...);
/* Types of all arguments and the number of arguments should match
* what the formatting specifies. */
return std::enable_if_t < fmt_nargs_valid.is_type_valid &&
(sizeof...(T) == fmt_nargs_valid.total_args),
void > ();
}
private:
/* Remove this after upgrading to C++20. */
template<typename T> using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<T>>;
/**
* Make #std::string etc., usable for fprintf-family.
* \return: `const char *` or the original argument if the argument is
* not related to #std::string.
*/
template<typename T> constexpr auto string_to_primitive(T &&arg) const
{
if constexpr (std::is_same_v<remove_cvref_t<T>, std::string> ||
std::is_same_v<remove_cvref_t<T>, blender::StringRefNull>) {
return arg.c_str();
}
else if constexpr (std::is_same_v<remove_cvref_t<T>, blender::StringRef>) {
BLI_STATIC_ASSERT(
(always_false<T>::value),
"Null-terminated string not present. Please use blender::StringRefNull instead.");
/* Another trick to cause a compile-time error: returning nothing to #std::printf. */
return;
}
else {
return std::forward<T>(arg);
}
}
template<int total_args, typename... T>
constexpr std::enable_if_t<(total_args != 0), void> write__impl(const char *fmt,
T &&...args) const
{
std::fprintf(outfile_, fmt, string_to_primitive(std::forward<T>(args))...);
}
template<int total_args, typename... T>
constexpr std::enable_if_t<(total_args == 0), void> write__impl(const char *fmt,
T &&...args) const
{
std::fputs(fmt, outfile_);
}
};
} // namespace blender::io::obj

View File

@ -0,0 +1,489 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup obj
*/
#include "BKE_customdata.h"
#include "BKE_deform.h"
#include "BKE_lib_id.h"
#include "BKE_material.h"
#include "BKE_mesh.h"
#include "BKE_mesh_mapping.h"
#include "BKE_object.h"
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "DEG_depsgraph_query.h"
#include "DNA_material_types.h"
#include "DNA_mesh_types.h"
#include "DNA_modifier_types.h"
#include "DNA_object_types.h"
#include "obj_export_mesh.hh"
namespace blender::io::obj {
/**
* Store evaluated Object and Mesh pointers. Conditionally triangulate a mesh, or
* create a new Mesh from a Curve.
*/
OBJMesh::OBJMesh(Depsgraph *depsgraph, const OBJExportParams &export_params, Object *mesh_object)
{
export_object_eval_ = DEG_get_evaluated_object(depsgraph, mesh_object);
export_mesh_eval_ = BKE_object_get_evaluated_mesh(export_object_eval_);
mesh_eval_needs_free_ = false;
if (!export_mesh_eval_) {
/* Curves and NURBS surfaces need a new mesh when they're
* exported in the form of vertices and edges.
*/
export_mesh_eval_ = BKE_mesh_new_from_object(depsgraph, export_object_eval_, true, true);
/* Since a new mesh been allocated, it needs to be freed in the destructor. */
mesh_eval_needs_free_ = true;
}
if (export_params.export_triangulated_mesh &&
ELEM(export_object_eval_->type, OB_MESH, OB_SURF)) {
std::tie(export_mesh_eval_, mesh_eval_needs_free_) = triangulate_mesh_eval();
}
set_world_axes_transform(export_params.forward_axis, export_params.up_axis);
}
/**
* Free new meshes allocated for triangulated meshes, or Curve converted to Mesh.
*/
OBJMesh::~OBJMesh()
{
free_mesh_if_needed();
if (poly_smooth_groups_) {
MEM_freeN(poly_smooth_groups_);
}
}
/**
* Free the mesh if _the exporter_ created it.
*/
void OBJMesh::free_mesh_if_needed()
{
if (mesh_eval_needs_free_ && export_mesh_eval_) {
BKE_id_free(nullptr, export_mesh_eval_);
}
}
/**
* Allocate a new Mesh with triangulated polygons.
*
* The returned mesh can be the same as the old one.
* \return Owning pointer to the new Mesh, and whether a new Mesh was created.
*/
std::pair<Mesh *, bool> OBJMesh::triangulate_mesh_eval()
{
if (export_mesh_eval_->totpoly <= 0) {
return {export_mesh_eval_, false};
}
const struct BMeshCreateParams bm_create_params = {0u};
const struct BMeshFromMeshParams bm_convert_params = {1u, 0, 0, 0};
/* Lower threshold where triangulation of a polygon starts, i.e. a quadrilateral will be
* triangulated here. */
const int triangulate_min_verts = 4;
unique_bmesh_ptr bmesh(
BKE_mesh_to_bmesh_ex(export_mesh_eval_, &bm_create_params, &bm_convert_params));
BM_mesh_triangulate(bmesh.get(),
MOD_TRIANGULATE_NGON_BEAUTY,
MOD_TRIANGULATE_QUAD_SHORTEDGE,
triangulate_min_verts,
false,
nullptr,
nullptr,
nullptr);
Mesh *triangulated = BKE_mesh_from_bmesh_for_eval_nomain(
bmesh.get(), nullptr, export_mesh_eval_);
free_mesh_if_needed();
return {triangulated, true};
}
/**
* Set the final transform after applying axes settings and an Object's world transform.
*/
void OBJMesh::set_world_axes_transform(const eTransformAxisForward forward,
const eTransformAxisUp up)
{
float axes_transform[3][3];
unit_m3(axes_transform);
/* +Y-forward and +Z-up are the default Blender axis settings. */
mat3_from_axis_conversion(OBJ_AXIS_Y_FORWARD, OBJ_AXIS_Z_UP, forward, up, axes_transform);
/* mat3_from_axis_conversion returns a transposed matrix! */
transpose_m3(axes_transform);
mul_m4_m3m4(world_and_axes_transform_, axes_transform, export_object_eval_->obmat);
/* mul_m4_m3m4 does not transform last row of obmat, i.e. location data. */
mul_v3_m3v3(world_and_axes_transform_[3], axes_transform, export_object_eval_->obmat[3]);
world_and_axes_transform_[3][3] = export_object_eval_->obmat[3][3];
}
int OBJMesh::tot_vertices() const
{
return export_mesh_eval_->totvert;
}
int OBJMesh::tot_polygons() const
{
return export_mesh_eval_->totpoly;
}
int OBJMesh::tot_uv_vertices() const
{
return tot_uv_vertices_;
}
int OBJMesh::tot_edges() const
{
return export_mesh_eval_->totedge;
}
/**
* \return Total materials in the object.
*/
int16_t OBJMesh::tot_materials() const
{
return export_mesh_eval_->totcol;
}
/**
* \return Smooth group of the polygon at the given index.
*/
int OBJMesh::ith_smooth_group(const int poly_index) const
{
/* Calculate smooth groups first: #OBJMesh::calc_smooth_groups. */
BLI_assert(tot_smooth_groups_ != -NEGATIVE_INIT);
BLI_assert(poly_smooth_groups_);
return poly_smooth_groups_[poly_index];
}
void OBJMesh::ensure_mesh_normals() const
{
BKE_mesh_ensure_normals(export_mesh_eval_);
BKE_mesh_calc_normals_split(export_mesh_eval_);
}
void OBJMesh::ensure_mesh_edges() const
{
BKE_mesh_calc_edges(export_mesh_eval_, true, false);
BKE_mesh_calc_edges_loose(export_mesh_eval_);
}
/**
* Calculate smooth groups of a smooth-shaded object.
* \return A polygon aligned array of smooth group numbers.
*/
void OBJMesh::calc_smooth_groups(const bool use_bitflags)
{
poly_smooth_groups_ = BKE_mesh_calc_smoothgroups(export_mesh_eval_->medge,
export_mesh_eval_->totedge,
export_mesh_eval_->mpoly,
export_mesh_eval_->totpoly,
export_mesh_eval_->mloop,
export_mesh_eval_->totloop,
&tot_smooth_groups_,
use_bitflags);
}
/**
* Return mat_nr-th material of the object. The given index should be zero-based.
*/
const Material *OBJMesh::get_object_material(const int16_t mat_nr) const
{
/* "+ 1" as material getter needs one-based indices. */
const Material *r_mat = BKE_object_material_get(export_object_eval_, mat_nr + 1);
#ifdef DEBUG
if (!r_mat) {
std::cerr << "Material not found for mat_nr = " << mat_nr << std::endl;
}
#endif
return r_mat;
}
bool OBJMesh::is_ith_poly_smooth(const int poly_index) const
{
return export_mesh_eval_->mpoly[poly_index].flag & ME_SMOOTH;
}
/**
* Returns a zero-based index of a polygon's material indexing into
* the Object's material slots.
*/
int16_t OBJMesh::ith_poly_matnr(const int poly_index) const
{
BLI_assert(poly_index < export_mesh_eval_->totpoly);
const int16_t r_mat_nr = export_mesh_eval_->mpoly[poly_index].mat_nr;
return r_mat_nr >= 0 ? r_mat_nr : NOT_FOUND;
}
/**
* Get object name as it appears in the outliner.
*/
const char *OBJMesh::get_object_name() const
{
return export_object_eval_->id.name + 2;
}
/**
* Get Object's Mesh's name.
*/
const char *OBJMesh::get_object_mesh_name() const
{
return export_mesh_eval_->id.name + 2;
}
/**
* Get object's material (at the given index) name. The given index should be zero-based.
*/
const char *OBJMesh::get_object_material_name(const int16_t mat_nr) const
{
const Material *mat = get_object_material(mat_nr);
if (!mat) {
return nullptr;
}
return mat->id.name + 2;
}
/**
* Calculate coordinates of the vertex at the given index.
*/
float3 OBJMesh::calc_vertex_coords(const int vert_index, const float scaling_factor) const
{
float3 r_coords;
copy_v3_v3(r_coords, export_mesh_eval_->mvert[vert_index].co);
mul_v3_fl(r_coords, scaling_factor);
mul_m4_v3(world_and_axes_transform_, r_coords);
return r_coords;
}
/**
* Calculate vertex indices of all vertices of the polygon at the given index.
*/
Vector<int> OBJMesh::calc_poly_vertex_indices(const int poly_index) const
{
const MPoly &mpoly = export_mesh_eval_->mpoly[poly_index];
const MLoop *mloop = &export_mesh_eval_->mloop[mpoly.loopstart];
const int totloop = mpoly.totloop;
Vector<int> r_poly_vertex_indices(totloop);
for (int loop_index = 0; loop_index < totloop; loop_index++) {
r_poly_vertex_indices[loop_index] = mloop[loop_index].v;
}
return r_poly_vertex_indices;
}
/**
* Calculate UV vertex coordinates of an Object.
*
* \note Also store the UV vertex indices in the member variable.
*/
void OBJMesh::store_uv_coords_and_indices(Vector<std::array<float, 2>> &r_uv_coords)
{
const MPoly *mpoly = export_mesh_eval_->mpoly;
const MLoop *mloop = export_mesh_eval_->mloop;
const int totpoly = export_mesh_eval_->totpoly;
const int totvert = export_mesh_eval_->totvert;
const MLoopUV *mloopuv = static_cast<MLoopUV *>(
CustomData_get_layer(&export_mesh_eval_->ldata, CD_MLOOPUV));
if (!mloopuv) {
tot_uv_vertices_ = 0;
return;
}
const float limit[2] = {STD_UV_CONNECT_LIMIT, STD_UV_CONNECT_LIMIT};
UvVertMap *uv_vert_map = BKE_mesh_uv_vert_map_create(
mpoly, mloop, mloopuv, totpoly, totvert, limit, false, false);
uv_indices_.resize(totpoly);
/* At least total vertices of a mesh will be present in its texture map. So
* reserve minimum space early. */
r_uv_coords.reserve(totvert);
tot_uv_vertices_ = 0;
for (int vertex_index = 0; vertex_index < totvert; vertex_index++) {
const UvMapVert *uv_vert = BKE_mesh_uv_vert_map_get_vert(uv_vert_map, vertex_index);
for (; uv_vert; uv_vert = uv_vert->next) {
if (uv_vert->separate) {
tot_uv_vertices_ += 1;
}
const int vertices_in_poly = mpoly[uv_vert->poly_index].totloop;
/* Store UV vertex coordinates. */
r_uv_coords.resize(tot_uv_vertices_);
const int loopstart = mpoly[uv_vert->poly_index].loopstart;
Span<float> vert_uv_coords(mloopuv[loopstart + uv_vert->loop_of_poly_index].uv, 2);
r_uv_coords[tot_uv_vertices_ - 1][0] = vert_uv_coords[0];
r_uv_coords[tot_uv_vertices_ - 1][1] = vert_uv_coords[1];
/* Store UV vertex indices. */
uv_indices_[uv_vert->poly_index].resize(vertices_in_poly);
/* Keep indices zero-based and let the writer handle the "+ 1" as per OBJ spec. */
uv_indices_[uv_vert->poly_index][uv_vert->loop_of_poly_index] = tot_uv_vertices_ - 1;
}
}
BKE_mesh_uv_vert_map_free(uv_vert_map);
}
Span<int> OBJMesh::calc_poly_uv_indices(const int poly_index) const
{
if (uv_indices_.size() <= 0) {
return {};
}
BLI_assert(poly_index < export_mesh_eval_->totpoly);
BLI_assert(poly_index < uv_indices_.size());
return uv_indices_[poly_index];
}
/**
* Calculate polygon normal of a polygon at given index.
*
* Should be used for flat-shaded polygons.
*/
float3 OBJMesh::calc_poly_normal(const int poly_index) const
{
float3 r_poly_normal;
const MPoly &poly = export_mesh_eval_->mpoly[poly_index];
const MLoop &mloop = export_mesh_eval_->mloop[poly.loopstart];
const MVert &mvert = *(export_mesh_eval_->mvert);
BKE_mesh_calc_poly_normal(&poly, &mloop, &mvert, r_poly_normal);
mul_mat3_m4_v3(world_and_axes_transform_, r_poly_normal);
return r_poly_normal;
}
/**
* Calculate loop normals of a polygon at the given index.
*
* Should be used for smooth-shaded polygons.
*/
void OBJMesh::calc_loop_normals(const int poly_index, Vector<float3> &r_loop_normals) const
{
r_loop_normals.clear();
const MPoly &mpoly = export_mesh_eval_->mpoly[poly_index];
const float(
*lnors)[3] = (const float(*)[3])(CustomData_get_layer(&export_mesh_eval_->ldata, CD_NORMAL));
for (int loop_of_poly = 0; loop_of_poly < mpoly.totloop; loop_of_poly++) {
float3 loop_normal;
copy_v3_v3(loop_normal, lnors[mpoly.loopstart + loop_of_poly]);
mul_mat3_m4_v3(world_and_axes_transform_, loop_normal);
r_loop_normals.append(loop_normal);
}
}
/**
* Calculate a polygon's polygon/loop normal indices.
* \param object_tot_prev_normals Number of normals of this Object written so far.
* \return Number of distinct normal indices.
*/
std::pair<int, Vector<int>> OBJMesh::calc_poly_normal_indices(
const int poly_index, const int object_tot_prev_normals) const
{
const MPoly &mpoly = export_mesh_eval_->mpoly[poly_index];
const int totloop = mpoly.totloop;
Vector<int> r_poly_normal_indices(totloop);
if (is_ith_poly_smooth(poly_index)) {
for (int poly_loop_index = 0; poly_loop_index < totloop; poly_loop_index++) {
/* Using polygon loop index is fine because polygon/loop normals and their normal indices are
* written by looping over #Mesh.mpoly /#Mesh.mloop in the same order. */
r_poly_normal_indices[poly_loop_index] = object_tot_prev_normals + poly_loop_index;
}
/* For a smooth-shaded polygon, #Mesh.totloop -many loop normals are written. */
return {totloop, r_poly_normal_indices};
}
for (int poly_loop_index = 0; poly_loop_index < totloop; poly_loop_index++) {
r_poly_normal_indices[poly_loop_index] = object_tot_prev_normals;
}
/* For a flat-shaded polygon, one polygon normal is written. */
return {1, r_poly_normal_indices};
}
/**
* Find the index of the vertex group with the maximum number of vertices in a polygon.
* The index indices into the #Object.defbase.
*
* If two or more groups have the same number of vertices (maximum), group name depends on the
* implementation of #std::max_element.
*/
int16_t OBJMesh::get_poly_deform_group_index(const int poly_index) const
{
BLI_assert(poly_index < export_mesh_eval_->totpoly);
const MPoly &mpoly = export_mesh_eval_->mpoly[poly_index];
const MLoop *mloop = &export_mesh_eval_->mloop[mpoly.loopstart];
const Object *obj = export_object_eval_;
const int tot_deform_groups = BKE_object_defgroup_count(obj);
/* Indices of the vector index into deform groups of an object; values are the]
* number of vertex members in one deform group. */
Vector<int16_t> deform_group_members(tot_deform_groups, 0);
/* Whether at least one vertex in the polygon belongs to any group. */
bool found_group = false;
const MDeformVert *dvert_orig = static_cast<MDeformVert *>(
CustomData_get_layer(&export_mesh_eval_->vdata, CD_MDEFORMVERT));
if (!dvert_orig) {
return NOT_FOUND;
}
const MDeformWeight *curr_weight = nullptr;
const MDeformVert *dvert = nullptr;
for (int loop_index = 0; loop_index < mpoly.totloop; loop_index++) {
dvert = &dvert_orig[(mloop + loop_index)->v];
curr_weight = dvert->dw;
if (curr_weight) {
bDeformGroup *vertex_group = static_cast<bDeformGroup *>(
BLI_findlink(BKE_object_defgroup_list(obj), curr_weight->def_nr));
if (vertex_group) {
deform_group_members[curr_weight->def_nr] += 1;
found_group = true;
}
}
}
if (!found_group) {
return NOT_FOUND;
}
/* Index of the group with maximum vertices. */
int16_t max_idx = std::max_element(deform_group_members.begin(), deform_group_members.end()) -
deform_group_members.begin();
return max_idx;
}
/**
* Find the name of the vertex deform group at the given index.
* The index indices into the #Object.defbase.
*/
const char *OBJMesh::get_poly_deform_group_name(const int16_t def_group_index) const
{
const bDeformGroup &vertex_group = *(static_cast<bDeformGroup *>(
BLI_findlink(BKE_object_defgroup_list(export_object_eval_), def_group_index)));
return vertex_group.name;
}
/**
* Calculate vertex indices of an edge's corners if it is a loose edge.
*/
std::optional<std::array<int, 2>> OBJMesh::calc_loose_edge_vert_indices(const int edge_index) const
{
const MEdge &edge = export_mesh_eval_->medge[edge_index];
if (edge.flag & ME_LOOSEEDGE) {
return std::array<int, 2>{static_cast<int>(edge.v1), static_cast<int>(edge.v2)};
}
return std::nullopt;
}
} // namespace blender::io::obj

View File

@ -0,0 +1,131 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup obj
*/
#pragma once
#include <optional>
#include "BLI_float3.hh"
#include "BLI_utility_mixins.hh"
#include "BLI_vector.hh"
#include "bmesh.h"
#include "bmesh_tools.h"
#include "DNA_material_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "IO_wavefront_obj.h"
namespace blender::io::obj {
/* Denote absence for usually non-negative numbers. */
const int NOT_FOUND = -1;
/* Any negative number other than `NOT_FOUND` to initialise usually non-negative numbers. */
const int NEGATIVE_INIT = -10;
/**
* #std::unique_ptr deleter for BMesh.
*/
struct CustomBMeshDeleter {
void operator()(BMesh *bmesh)
{
if (bmesh) {
BM_mesh_free(bmesh);
}
}
};
using unique_bmesh_ptr = std::unique_ptr<BMesh, CustomBMeshDeleter>;
class OBJMesh : NonCopyable {
private:
Object *export_object_eval_;
Mesh *export_mesh_eval_;
/**
* For curves which are converted to mesh, and triangulated meshes, a new mesh is allocated.
*/
bool mesh_eval_needs_free_ = false;
/**
* Final transform of an object obtained from export settings (up_axis, forward_axis) and the
* object's world transform matrix.
*/
float world_and_axes_transform_[4][4];
/**
* Total UV vertices in a mesh's texture map.
*/
int tot_uv_vertices_ = 0;
/**
* Per-polygon-per-vertex UV vertex indices.
*/
Vector<Vector<int>> uv_indices_;
/**
* Total smooth groups in an object.
*/
int tot_smooth_groups_ = NEGATIVE_INIT;
/**
* Polygon aligned array of their smooth groups.
*/
int *poly_smooth_groups_ = nullptr;
public:
OBJMesh(Depsgraph *depsgraph, const OBJExportParams &export_params, Object *mesh_object);
~OBJMesh();
int tot_vertices() const;
int tot_polygons() const;
int tot_uv_vertices() const;
int tot_edges() const;
int16_t tot_materials() const;
const Material *get_object_material(const int16_t mat_nr) const;
int16_t ith_poly_matnr(const int poly_index) const;
void ensure_mesh_normals() const;
void ensure_mesh_edges() const;
void calc_smooth_groups(const bool use_bitflags);
int ith_smooth_group(const int poly_index) const;
bool is_ith_poly_smooth(const int poly_index) const;
const char *get_object_name() const;
const char *get_object_mesh_name() const;
const char *get_object_material_name(const int16_t mat_nr) const;
float3 calc_vertex_coords(const int vert_index, const float scaling_factor) const;
Vector<int> calc_poly_vertex_indices(const int poly_index) const;
void store_uv_coords_and_indices(Vector<std::array<float, 2>> &r_uv_coords);
Span<int> calc_poly_uv_indices(const int poly_index) const;
float3 calc_poly_normal(const int poly_index) const;
std::pair<int, Vector<int>> calc_poly_normal_indices(const int poly_index,
const int object_tot_prev_normals) const;
void calc_loop_normals(const int poly_index, Vector<float3> &r_loop_normals) const;
int16_t get_poly_deform_group_index(const int poly_index) const;
const char *get_poly_deform_group_name(const int16_t def_group_index) const;
std::optional<std::array<int, 2>> calc_loose_edge_vert_indices(const int edge_index) const;
private:
void free_mesh_if_needed();
std::pair<Mesh *, bool> triangulate_mesh_eval();
void set_world_axes_transform(const eTransformAxisForward forward, const eTransformAxisUp up);
};
} // namespace blender::io::obj

View File

@ -0,0 +1,362 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup obj
*/
#include "BKE_image.h"
#include "BKE_node.h"
#include "BLI_float3.hh"
#include "BLI_map.hh"
#include "BLI_path_util.h"
#include "DNA_material_types.h"
#include "DNA_node_types.h"
#include "NOD_node_tree_ref.hh"
#include "obj_export_mesh.hh"
#include "obj_export_mtl.hh"
namespace blender::io::obj {
/**
* Copy a float property of the given type from the bNode to given buffer.
*/
static void copy_property_from_node(const eNodeSocketDatatype property_type,
const bNode *node,
const char *identifier,
MutableSpan<float> r_property)
{
if (!node) {
return;
}
bNodeSocket *socket{nodeFindSocket(node, SOCK_IN, identifier)};
BLI_assert(socket && socket->type == property_type);
if (!socket) {
return;
}
switch (property_type) {
case SOCK_FLOAT: {
BLI_assert(r_property.size() == 1);
bNodeSocketValueFloat *socket_def_value = static_cast<bNodeSocketValueFloat *>(
socket->default_value);
r_property[0] = socket_def_value->value;
break;
}
case SOCK_RGBA: {
BLI_assert(r_property.size() == 3);
bNodeSocketValueRGBA *socket_def_value = static_cast<bNodeSocketValueRGBA *>(
socket->default_value);
copy_v3_v3(r_property.data(), socket_def_value->value);
break;
}
case SOCK_VECTOR: {
BLI_assert(r_property.size() == 3);
bNodeSocketValueVector *socket_def_value = static_cast<bNodeSocketValueVector *>(
socket->default_value);
copy_v3_v3(r_property.data(), socket_def_value->value);
break;
}
default: {
/* Other socket types are not handled here. */
BLI_assert(0);
break;
}
}
}
/**
* Collect all the source sockets linked to the destination socket in a destination node.
*/
static void linked_sockets_to_dest_id(const bNode *dest_node,
const nodes::NodeTreeRef &node_tree,
StringRefNull dest_socket_id,
Vector<const nodes::OutputSocketRef *> &r_linked_sockets)
{
r_linked_sockets.clear();
if (!dest_node) {
return;
}
Span<const nodes::NodeRef *> object_dest_nodes = node_tree.nodes_by_type(dest_node->idname);
Span<const nodes::InputSocketRef *> dest_inputs = object_dest_nodes.first()->inputs();
const nodes::InputSocketRef *dest_socket = nullptr;
for (const nodes::InputSocketRef *curr_socket : dest_inputs) {
if (STREQ(curr_socket->bsocket()->identifier, dest_socket_id.c_str())) {
dest_socket = curr_socket;
break;
}
}
if (dest_socket) {
Span<const nodes::OutputSocketRef *> linked_sockets = dest_socket->directly_linked_sockets();
r_linked_sockets.resize(linked_sockets.size());
r_linked_sockets = linked_sockets;
}
}
/**
* From a list of sockets, get the parent node which is of the given node type.
*/
static const bNode *get_node_of_type(Span<const nodes::OutputSocketRef *> sockets_list,
const int node_type)
{
for (const nodes::SocketRef *socket : sockets_list) {
const bNode *parent_node = socket->bnode();
if (parent_node->typeinfo->type == node_type) {
return parent_node;
}
}
return nullptr;
}
/**
* From a texture image shader node, get the image's filepath.
* Returned filepath is stripped of initial "//". If packed image is found,
* only the file "name" is returned.
*/
static const char *get_image_filepath(const bNode *tex_node)
{
if (!tex_node) {
return nullptr;
}
Image *tex_image = reinterpret_cast<Image *>(tex_node->id);
if (!tex_image || !BKE_image_has_filepath(tex_image)) {
return nullptr;
}
const char *path = tex_image->filepath;
if (BKE_image_has_packedfile(tex_image)) {
/* Put image in the same directory as the .MTL file. */
path = BLI_path_slash_rfind(path) + 1;
fprintf(stderr,
"Packed image found:'%s'. Unpack and place the image in the same "
"directory as the .MTL file.\n",
path);
}
if (path[0] == '/' && path[1] == '/') {
path += 2;
}
return path;
}
/**
* Find the Principled-BSDF Node in nodetree.
* We only want one that feeds directly into a Material Output node
* (that is the behavior of the legacy Python exporter).
*/
static const nodes::NodeRef *find_bsdf_node(const nodes::NodeTreeRef *nodetree)
{
if (!nodetree) {
return nullptr;
}
for (const nodes::NodeRef *node : nodetree->nodes_by_type("ShaderNodeOutputMaterial")) {
const nodes::InputSocketRef *node_input_socket0 = node->inputs()[0];
for (const nodes::OutputSocketRef *out_sock : node_input_socket0->directly_linked_sockets()) {
const nodes::NodeRef &in_node = out_sock->node();
if (in_node.typeinfo()->type == SH_NODE_BSDF_PRINCIPLED) {
return &in_node;
}
}
}
return nullptr;
}
/**
* Store properties found either in bNode or material into r_mtl_mat.
*/
static void store_bsdf_properties(const nodes::NodeRef *bsdf_node,
const Material *material,
MTLMaterial &r_mtl_mat)
{
const bNode *bnode = nullptr;
if (bsdf_node) {
bnode = bsdf_node->bnode();
}
/* If p-BSDF is not present, fallback to #Object.Material. */
float roughness = material->roughness;
if (bnode) {
copy_property_from_node(SOCK_FLOAT, bnode, "Roughness", {&roughness, 1});
}
/* Emperical approximation. Importer should use the inverse of this method. */
float spec_exponent = (1.0f - roughness) * 30;
spec_exponent *= spec_exponent;
float specular = material->spec;
if (bnode) {
copy_property_from_node(SOCK_FLOAT, bnode, "Specular", {&specular, 1});
}
float metallic = material->metallic;
if (bnode) {
copy_property_from_node(SOCK_FLOAT, bnode, "Metallic", {&metallic, 1});
}
float refraction_index = 1.0f;
if (bnode) {
copy_property_from_node(SOCK_FLOAT, bnode, "IOR", {&refraction_index, 1});
}
float dissolved = material->a;
if (bnode) {
copy_property_from_node(SOCK_FLOAT, bnode, "Alpha", {&dissolved, 1});
}
const bool transparent = dissolved != 1.0f;
float3 diffuse_col = {material->r, material->g, material->b};
if (bnode) {
copy_property_from_node(SOCK_RGBA, bnode, "Base Color", {diffuse_col, 3});
}
float3 emission_col{0.0f};
float emission_strength = 0.0f;
if (bnode) {
copy_property_from_node(SOCK_FLOAT, bnode, "Emission Strength", {&emission_strength, 1});
copy_property_from_node(SOCK_RGBA, bnode, "Emission", {emission_col, 3});
}
mul_v3_fl(emission_col, emission_strength);
/* See https://wikipedia.org/wiki/Wavefront_.obj_file for all possible values of illum. */
/* Highlight on. */
int illum = 2;
if (specular == 0.0f) {
/* Color on and Ambient on. */
illum = 1;
}
else if (metallic > 0.0f) {
/* Metallic ~= Reflection. */
if (transparent) {
/* Transparency: Refraction on, Reflection: ~~Fresnel off and Ray trace~~ on. */
illum = 6;
}
else {
/* Reflection on and Ray trace on. */
illum = 3;
}
}
else if (transparent) {
/* Transparency: Glass on, Reflection: Ray trace off */
illum = 9;
}
r_mtl_mat.Ns = spec_exponent;
if (metallic != 0.0f) {
r_mtl_mat.Ka = {metallic, metallic, metallic};
}
else {
r_mtl_mat.Ka = {1.0f, 1.0f, 1.0f};
}
r_mtl_mat.Kd = diffuse_col;
r_mtl_mat.Ks = {specular, specular, specular};
r_mtl_mat.Ke = emission_col;
r_mtl_mat.Ni = refraction_index;
r_mtl_mat.d = dissolved;
r_mtl_mat.illum = illum;
}
/**
* Store image texture options and filepaths in r_mtl_mat.
*/
static void store_image_textures(const nodes::NodeRef *bsdf_node,
const nodes::NodeTreeRef *node_tree,
const Material *material,
MTLMaterial &r_mtl_mat)
{
if (!material || !node_tree || !bsdf_node) {
/* No nodetree, no images, or no Principled BSDF node. */
return;
}
const bNode *bnode = bsdf_node->bnode();
/* Normal Map Texture has two extra tasks of:
* - finding a Normal Map node before finding a texture node.
* - finding "Strength" property of the node for `-bm` option.
*/
for (Map<const eMTLSyntaxElement, tex_map_XX>::MutableItem texture_map :
r_mtl_mat.texture_maps.items()) {
Vector<const nodes::OutputSocketRef *> linked_sockets;
const bNode *normal_map_node{nullptr};
if (texture_map.key == eMTLSyntaxElement::map_Bump) {
/* Find sockets linked to destination "Normal" socket in p-bsdf node. */
linked_sockets_to_dest_id(bnode, *node_tree, "Normal", linked_sockets);
/* Among the linked sockets, find Normal Map shader node. */
normal_map_node = get_node_of_type(linked_sockets, SH_NODE_NORMAL_MAP);
/* Find sockets linked to "Color" socket in normal map node. */
linked_sockets_to_dest_id(normal_map_node, *node_tree, "Color", linked_sockets);
}
else if (texture_map.key == eMTLSyntaxElement::map_Ke) {
float emission_strength = 0.0f;
copy_property_from_node(SOCK_FLOAT, bnode, "Emission Strength", {&emission_strength, 1});
if (emission_strength == 0.0f) {
continue;
}
}
else {
/* Find sockets linked to the destination socket of interest, in p-bsdf node. */
linked_sockets_to_dest_id(
bnode, *node_tree, texture_map.value.dest_socket_id, linked_sockets);
}
/* Among the linked sockets, find Image Texture shader node. */
const bNode *tex_node{get_node_of_type(linked_sockets, SH_NODE_TEX_IMAGE)};
if (!tex_node) {
continue;
}
const char *tex_image_filepath = get_image_filepath(tex_node);
if (!tex_image_filepath) {
continue;
}
/* Find "Mapping" node if connected to texture node. */
linked_sockets_to_dest_id(tex_node, *node_tree, "Vector", linked_sockets);
const bNode *mapping = get_node_of_type(linked_sockets, SH_NODE_MAPPING);
if (normal_map_node) {
copy_property_from_node(
SOCK_FLOAT, normal_map_node, "Strength", {&r_mtl_mat.map_Bump_strength, 1});
}
/* Texture transform options. Only translation (origin offset, "-o") and scale
* ("-o") are supported. */
copy_property_from_node(SOCK_VECTOR, mapping, "Location", {texture_map.value.translation, 3});
copy_property_from_node(SOCK_VECTOR, mapping, "Scale", {texture_map.value.scale, 3});
texture_map.value.image_path = tex_image_filepath;
}
}
MTLMaterial mtlmaterial_for_material(const Material *material)
{
BLI_assert(material != nullptr);
MTLMaterial mtlmat;
mtlmat.name = std::string(material->id.name + 2);
std::replace(mtlmat.name.begin(), mtlmat.name.end(), ' ', '_');
const nodes::NodeTreeRef *nodetree = nullptr;
if (material->nodetree) {
nodetree = new nodes::NodeTreeRef(material->nodetree);
}
const nodes::NodeRef *bsdf_node = find_bsdf_node(nodetree);
store_bsdf_properties(bsdf_node, material, mtlmat);
store_image_textures(bsdf_node, nodetree, material, mtlmat);
if (nodetree) {
delete nodetree;
}
return mtlmat;
}
} // namespace blender::io::obj

View File

@ -0,0 +1,104 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup obj
*/
#pragma once
#include "BLI_float3.hh"
#include "BLI_map.hh"
#include "BLI_string_ref.hh"
#include "BLI_vector.hh"
#include "DNA_node_types.h"
#include "obj_export_io.hh"
namespace blender {
template<> struct DefaultHash<io::obj::eMTLSyntaxElement> {
uint64_t operator()(const io::obj::eMTLSyntaxElement value) const
{
return static_cast<uint64_t>(value);
}
};
} // namespace blender
namespace blender::io::obj {
class OBJMesh;
/**
* Generic container for texture node properties.
*/
struct tex_map_XX {
tex_map_XX(StringRef to_socket_id) : dest_socket_id(to_socket_id){};
/** Target socket which this texture node connects to. */
const std::string dest_socket_id;
float3 translation{0.0f};
float3 scale{1.0f};
/* Only Flat and Smooth projections are supported. */
int projection_type = SHD_PROJ_FLAT;
std::string image_path;
std::string mtl_dir_path;
};
/**
* Container suited for storing Material data for/from a .MTL file.
*/
struct MTLMaterial {
MTLMaterial()
{
texture_maps.add(eMTLSyntaxElement::map_Kd, tex_map_XX("Base Color"));
texture_maps.add(eMTLSyntaxElement::map_Ks, tex_map_XX("Specular"));
texture_maps.add(eMTLSyntaxElement::map_Ns, tex_map_XX("Roughness"));
texture_maps.add(eMTLSyntaxElement::map_d, tex_map_XX("Alpha"));
texture_maps.add(eMTLSyntaxElement::map_refl, tex_map_XX("Metallic"));
texture_maps.add(eMTLSyntaxElement::map_Ke, tex_map_XX("Emission"));
texture_maps.add(eMTLSyntaxElement::map_Bump, tex_map_XX("Normal"));
}
/**
* Caller must ensure that the given lookup key exists in the Map.
* \return Texture map corresponding to the given ID.
*/
tex_map_XX &tex_map_of_type(const eMTLSyntaxElement key)
{
{
BLI_assert(texture_maps.contains_as(key));
return texture_maps.lookup_as(key);
}
}
std::string name;
/* Always check for negative values while importing or exporting. Use defaults if
* any value is negative. */
float Ns{-1.0f};
float3 Ka{-1.0f};
float3 Kd{-1.0f};
float3 Ks{-1.0f};
float3 Ke{-1.0f};
float Ni{-1.0f};
float d{-1.0f};
int illum{-1};
Map<const eMTLSyntaxElement, tex_map_XX> texture_maps;
/** Only used for Normal Map node: "map_Bump". */
float map_Bump_strength{-1.0f};
};
MTLMaterial mtlmaterial_for_material(const Material *material);
} // namespace blender::io::obj

View File

@ -0,0 +1,122 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup obj
*/
#include "BLI_float3.hh"
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
#include "IO_wavefront_obj.h"
#include "obj_export_nurbs.hh"
namespace blender::io::obj {
OBJCurve::OBJCurve(const Depsgraph *depsgraph,
const OBJExportParams &export_params,
Object *curve_object)
: export_object_eval_(curve_object)
{
export_object_eval_ = DEG_get_evaluated_object(depsgraph, curve_object);
export_curve_ = static_cast<Curve *>(export_object_eval_->data);
set_world_axes_transform(export_params.forward_axis, export_params.up_axis);
}
/**
* Set the final transform after applying axes settings and an Object's world transform.
*/
void OBJCurve::set_world_axes_transform(const eTransformAxisForward forward,
const eTransformAxisUp up)
{
float axes_transform[3][3];
unit_m3(axes_transform);
/* +Y-forward and +Z-up are the Blender's default axis settings. */
mat3_from_axis_conversion(OBJ_AXIS_Y_FORWARD, OBJ_AXIS_Z_UP, forward, up, axes_transform);
/* mat3_from_axis_conversion returns a transposed matrix! */
transpose_m3(axes_transform);
mul_m4_m3m4(world_axes_transform_, axes_transform, export_object_eval_->obmat);
/* #mul_m4_m3m4 does not transform last row of #Object.obmat, i.e. location data. */
mul_v3_m3v3(world_axes_transform_[3], axes_transform, export_object_eval_->obmat[3]);
world_axes_transform_[3][3] = export_object_eval_->obmat[3][3];
}
const char *OBJCurve::get_curve_name() const
{
return export_object_eval_->id.name + 2;
}
int OBJCurve::total_splines() const
{
return BLI_listbase_count(&export_curve_->nurb);
}
/**
* \param spline_index: Zero-based index of spline of interest.
* \return: Total vertices in a spline.
*/
int OBJCurve::total_spline_vertices(const int spline_index) const
{
const Nurb *const nurb = static_cast<Nurb *>(BLI_findlink(&export_curve_->nurb, spline_index));
return nurb->pntsu * nurb->pntsv;
}
/**
* Get coordinates of the vertex at the given index on the given spline.
*/
float3 OBJCurve::vertex_coordinates(const int spline_index,
const int vertex_index,
const float scaling_factor) const
{
const Nurb *const nurb = static_cast<Nurb *>(BLI_findlink(&export_curve_->nurb, spline_index));
float3 r_coord;
const BPoint &bpoint = nurb->bp[vertex_index];
copy_v3_v3(r_coord, bpoint.vec);
mul_m4_v3(world_axes_transform_, r_coord);
mul_v3_fl(r_coord, scaling_factor);
return r_coord;
}
/**
* Get total control points of the NURBS spline at the given index. This is different than total
* vertices of a spline.
*/
int OBJCurve::total_spline_control_points(const int spline_index) const
{
const Nurb *const nurb = static_cast<Nurb *>(BLI_findlink(&export_curve_->nurb, spline_index));
const int r_nurbs_degree = nurb->orderu - 1;
/* Total control points = Number of points in the curve (+ degree of the
* curve if it is cyclic). */
int r_tot_control_points = nurb->pntsv * nurb->pntsu;
if (nurb->flagu & CU_NURB_CYCLIC) {
r_tot_control_points += r_nurbs_degree;
}
return r_tot_control_points;
}
/**
* Get the degree of the NURBS spline at the given index.
*/
int OBJCurve::get_nurbs_degree(const int spline_index) const
{
const Nurb *const nurb = static_cast<Nurb *>(BLI_findlink(&export_curve_->nurb, spline_index));
return nurb->orderu - 1;
}
} // namespace blender::io::obj

View File

@ -0,0 +1,57 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup obj
*/
#pragma once
#include "BLI_utility_mixins.hh"
#include "DNA_curve_types.h"
namespace blender::io::obj {
/**
* Provides access to the a Curve Object's properties.
* Only #CU_NURBS type is supported.
*
* \note Used for Curves to be exported in parameter form, and not converted to meshes.
*/
class OBJCurve : NonCopyable {
private:
const Object *export_object_eval_;
const Curve *export_curve_;
float world_axes_transform_[4][4];
public:
OBJCurve(const Depsgraph *depsgraph, const OBJExportParams &export_params, Object *curve_object);
const char *get_curve_name() const;
int total_splines() const;
int total_spline_vertices(const int spline_index) const;
float3 vertex_coordinates(const int spline_index,
const int vertex_index,
const float scaling_factor) const;
int total_spline_control_points(const int spline_index) const;
int get_nurbs_degree(const int spline_index) const;
private:
void set_world_axes_transform(const eTransformAxisForward forward, const eTransformAxisUp up);
};
} // namespace blender::io::obj

View File

@ -0,0 +1,302 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup obj
*/
#include <cstdio>
#include <exception>
#include <memory>
#include "BKE_scene.h"
#include "BLI_path_util.h"
#include "BLI_vector.hh"
#include "DEG_depsgraph_query.h"
#include "DNA_scene_types.h"
#include "ED_object.h"
#include "obj_export_mesh.hh"
#include "obj_export_nurbs.hh"
#include "obj_exporter.hh"
#include "obj_export_file_writer.hh"
namespace blender::io::obj {
OBJDepsgraph::OBJDepsgraph(const bContext *C, const eEvaluationMode eval_mode)
{
Scene *scene = CTX_data_scene(C);
Main *bmain = CTX_data_main(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
if (eval_mode == DAG_EVAL_RENDER) {
depsgraph_ = DEG_graph_new(bmain, scene, view_layer, DAG_EVAL_RENDER);
needs_free_ = true;
DEG_graph_build_for_all_objects(depsgraph_);
BKE_scene_graph_evaluated_ensure(depsgraph_, bmain);
}
else {
depsgraph_ = CTX_data_ensure_evaluated_depsgraph(C);
needs_free_ = false;
}
}
OBJDepsgraph::~OBJDepsgraph()
{
if (needs_free_) {
DEG_graph_free(depsgraph_);
}
}
Depsgraph *OBJDepsgraph::get()
{
return depsgraph_;
}
void OBJDepsgraph::update_for_newframe()
{
BKE_scene_graph_update_for_newframe(depsgraph_);
}
static void print_exception_error(const std::system_error &ex)
{
std::cerr << ex.code().category().name() << ": " << ex.what() << ": " << ex.code().message()
<< std::endl;
}
/**
* Filter supported objects from the Scene.
*
* \note Curves are also stored with Meshes if export settings specify so.
*/
std::pair<Vector<std::unique_ptr<OBJMesh>>, Vector<std::unique_ptr<OBJCurve>>>
filter_supported_objects(Depsgraph *depsgraph, const OBJExportParams &export_params)
{
Vector<std::unique_ptr<OBJMesh>> r_exportable_meshes;
Vector<std::unique_ptr<OBJCurve>> r_exportable_nurbs;
const ViewLayer *view_layer = DEG_get_input_view_layer(depsgraph);
LISTBASE_FOREACH (const Base *, base, &view_layer->object_bases) {
Object *object_in_layer = base->object;
if (export_params.export_selected_objects && !(object_in_layer->base_flag & BASE_SELECTED)) {
continue;
}
switch (object_in_layer->type) {
case OB_SURF:
/* Export in mesh form: vertices and polygons. */
ATTR_FALLTHROUGH;
case OB_MESH:
r_exportable_meshes.append(
std::make_unique<OBJMesh>(depsgraph, export_params, object_in_layer));
break;
case OB_CURVE: {
Curve *curve = static_cast<Curve *>(object_in_layer->data);
Nurb *nurb{static_cast<Nurb *>(curve->nurb.first)};
if (!nurb) {
/* An empty curve. Not yet supported to export these as meshes. */
if (export_params.export_curves_as_nurbs) {
r_exportable_nurbs.append(
std::make_unique<OBJCurve>(depsgraph, export_params, object_in_layer));
}
break;
}
switch (nurb->type) {
case CU_NURBS:
if (export_params.export_curves_as_nurbs) {
/* Export in parameter form: control points. */
r_exportable_nurbs.append(
std::make_unique<OBJCurve>(depsgraph, export_params, object_in_layer));
}
else {
/* Export in mesh form: edges and vertices. */
r_exportable_meshes.append(
std::make_unique<OBJMesh>(depsgraph, export_params, object_in_layer));
}
break;
case CU_BEZIER:
/* Always export in mesh form: edges and vertices. */
r_exportable_meshes.append(
std::make_unique<OBJMesh>(depsgraph, export_params, object_in_layer));
break;
default:
/* Other curve types are not supported. */
break;
}
break;
}
default:
/* Other object types are not supported. */
break;
}
}
return {std::move(r_exportable_meshes), std::move(r_exportable_nurbs)};
}
static void write_mesh_objects(Vector<std::unique_ptr<OBJMesh>> exportable_as_mesh,
OBJWriter &obj_writer,
MTLWriter *mtl_writer,
const OBJExportParams &export_params)
{
if (mtl_writer) {
obj_writer.write_mtllib_name(mtl_writer->mtl_file_path());
}
/* Smooth groups and UV vertex indices may make huge memory allocations, so they should be freed
* right after they're written, instead of waiting for #blender::Vector to clean them up after
* all the objects are exported. */
for (auto &obj_mesh : exportable_as_mesh) {
obj_writer.write_object_name(*obj_mesh);
obj_writer.write_vertex_coords(*obj_mesh);
Vector<int> obj_mtlindices;
if (obj_mesh->tot_polygons() > 0) {
if (export_params.export_smooth_groups) {
obj_mesh->calc_smooth_groups(export_params.smooth_groups_bitflags);
}
if (export_params.export_normals) {
obj_writer.write_poly_normals(*obj_mesh);
}
if (export_params.export_uv) {
obj_writer.write_uv_coords(*obj_mesh);
}
if (mtl_writer) {
obj_mtlindices = mtl_writer->add_materials(*obj_mesh);
}
/* This function takes a 0-indexed slot index for the obj_mesh object and
* returns the material name that we are using in the .obj file for it. */
std::function<const char *(int)> matname_fn = [&](int s) -> const char * {
if (!mtl_writer || s < 0 || s >= obj_mtlindices.size()) {
return nullptr;
}
return mtl_writer->mtlmaterial_name(obj_mtlindices[s]);
};
obj_writer.write_poly_elements(*obj_mesh, matname_fn);
}
obj_writer.write_edges_indices(*obj_mesh);
obj_writer.update_index_offsets(*obj_mesh);
}
}
/**
* Export NURBS Curves in parameter form, not as vertices and edges.
*/
static void write_nurbs_curve_objects(const Vector<std::unique_ptr<OBJCurve>> &exportable_as_nurbs,
const OBJWriter &obj_writer)
{
/* #OBJCurve doesn't have any dynamically allocated memory, so it's fine
* to wait for #blender::Vector to clean the objects up. */
for (const std::unique_ptr<OBJCurve> &obj_curve : exportable_as_nurbs) {
obj_writer.write_nurbs_curve(*obj_curve);
}
}
/**
* Export a single frame to a .OBJ file.
*
* Conditionally write a .MTL file also.
*/
void export_frame(Depsgraph *depsgraph, const OBJExportParams &export_params, const char *filepath)
{
std::unique_ptr<OBJWriter> frame_writer = nullptr;
try {
frame_writer = std::make_unique<OBJWriter>(filepath, export_params);
}
catch (const std::system_error &ex) {
print_exception_error(ex);
return;
}
if (!frame_writer) {
BLI_assert(!"File should be writable by now.");
return;
}
std::unique_ptr<MTLWriter> mtl_writer = nullptr;
if (export_params.export_materials) {
try {
mtl_writer = std::make_unique<MTLWriter>(export_params.filepath);
}
catch (const std::system_error &ex) {
print_exception_error(ex);
}
}
frame_writer->write_header();
auto [exportable_as_mesh, exportable_as_nurbs] = filter_supported_objects(depsgraph,
export_params);
write_mesh_objects(
std::move(exportable_as_mesh), *frame_writer, mtl_writer.get(), export_params);
if (mtl_writer) {
mtl_writer->write_header(export_params.blen_filepath);
mtl_writer->write_materials();
}
write_nurbs_curve_objects(std::move(exportable_as_nurbs), *frame_writer);
}
/**
* Append the current frame number in the .OBJ file name.
*
* \return Whether the filepath is in #FILE_MAX limits.
*/
bool append_frame_to_filename(const char *filepath, const int frame, char *r_filepath_with_frames)
{
BLI_strncpy(r_filepath_with_frames, filepath, FILE_MAX);
BLI_path_extension_replace(r_filepath_with_frames, FILE_MAX, "");
const int digits = frame == 0 ? 1 : integer_digits_i(abs(frame));
BLI_path_frame(r_filepath_with_frames, frame, digits);
return BLI_path_extension_replace(r_filepath_with_frames, FILE_MAX, ".obj");
}
/**
* Central internal function to call Scene update & writer functions.
*/
void exporter_main(bContext *C, const OBJExportParams &export_params)
{
ED_object_mode_set(C, OB_MODE_OBJECT);
OBJDepsgraph obj_depsgraph(C, export_params.export_eval_mode);
Scene *scene = DEG_get_input_scene(obj_depsgraph.get());
const char *filepath = export_params.filepath;
/* Single frame export, i.e. no animation. */
if (!export_params.export_animation) {
fprintf(stderr, "Writing to %s\n", filepath);
export_frame(obj_depsgraph.get(), export_params, filepath);
return;
}
char filepath_with_frames[FILE_MAX];
/* Used to reset the Scene to its original state. */
const int original_frame = CFRA;
for (int frame = export_params.start_frame; frame <= export_params.end_frame; frame++) {
const bool filepath_ok = append_frame_to_filename(filepath, frame, filepath_with_frames);
if (!filepath_ok) {
fprintf(stderr, "Error: File Path too long.\n%s\n", filepath_with_frames);
return;
}
CFRA = frame;
obj_depsgraph.update_for_newframe();
fprintf(stderr, "Writing to %s\n", filepath_with_frames);
export_frame(obj_depsgraph.get(), export_params, filepath_with_frames);
}
CFRA = original_frame;
}
} // namespace blender::io::obj

View File

@ -0,0 +1,88 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup obj
*/
#pragma once
#include "BLI_utility_mixins.hh"
#include "BLI_vector.hh"
#include "IO_wavefront_obj.h"
namespace blender::io::obj {
/**
* Behaves like `std::unique_ptr<Depsgraph, custom_deleter>`.
* Needed to free a new Depsgraph created for #DAG_EVAL_RENDER.
*/
class OBJDepsgraph : NonMovable, NonCopyable {
private:
Depsgraph *depsgraph_ = nullptr;
bool needs_free_ = false;
public:
OBJDepsgraph(const bContext *C, const eEvaluationMode eval_mode);
~OBJDepsgraph();
Depsgraph *get();
void update_for_newframe();
};
/**
* The main function for exporting a .obj file according to the given `export_parameters`.
* It uses the context `C` to get the dependency graph, and from that, the `Scene`.
* Depending on whether or not `export_params.export_animation` is set, it writes
* either one file per animation frame, or just one file.
*/
void exporter_main(bContext *C, const OBJExportParams &export_params);
class OBJMesh;
class OBJCurve;
/**
* Export a single frame of a .obj file, according to the given `export_parameters`.
* The frame state is given in `depsgraph`.
* The output file name is given by `filepath`.
* This function is normally called from `exporter_main`, but is exposed here for testing purposes.
*/
void export_frame(Depsgraph *depsgraph,
const OBJExportParams &export_params,
const char *filepath);
/**
* Find the objects to be exported in the `view_layer` of the dependency graph`depsgraph`,
* and return them in vectors `unique_ptr`s of `OBJMesh` and `OBJCurve`.
* If `export_params.export_selected_objects` is set, then only selected objects are to be
* exported, else all objects are to be exported. But only objects of type `OB_MESH`, `OB_CURVE`,
* and `OB_SURF` are supported; the rest will be ignored. If `export_params.export_curves_as_nurbs`
* is set, then curves of type `CU_NURBS` are exported in curve form in the .obj file, otherwise
* they are converted to mesh and returned in the `OBJMesh` vector. All other exportable types are
* always converted to mesh and returned in the `OBJMesh` vector.
*/
std::pair<Vector<std::unique_ptr<OBJMesh>>, Vector<std::unique_ptr<OBJCurve>>>
filter_supported_objects(Depsgraph *depsgraph, const OBJExportParams &export_params);
/**
* Makes `r_filepath_with_frames` (which should point at a character array of size `FILE_MAX`)
* be `filepath` with its "#" characters replaced by the number representing `frame`, and with
* a .obj extension.
*/
bool append_frame_to_filename(const char *filepath, const int frame, char *r_filepath_with_frames);
} // namespace blender::io::obj

View File

@ -0,0 +1,417 @@
/* Apache License, Version 2.0 */
#include <fstream>
#include <gtest/gtest.h>
#include <ios>
#include <memory>
#include <sstream>
#include <string>
#include <system_error>
#include "testing/testing.h"
#include "tests/blendfile_loading_base_test.h"
#include "BKE_appdir.h"
#include "BKE_blender_version.h"
#include "BLI_fileops.h"
#include "BLI_index_range.hh"
#include "BLI_string_utf8.h"
#include "BLI_vector.hh"
#include "DEG_depsgraph.h"
#include "obj_export_file_writer.hh"
#include "obj_export_mesh.hh"
#include "obj_export_nurbs.hh"
#include "obj_exporter.hh"
#include "obj_exporter_tests.hh"
namespace blender::io::obj {
/* This is also the test name. */
class obj_exporter_test : public BlendfileLoadingBaseTest {
public:
/**
* \param filepath: relative to "tests" directory.
*/
bool load_file_and_depsgraph(const std::string &filepath,
const eEvaluationMode eval_mode = DAG_EVAL_VIEWPORT)
{
if (!blendfile_load(filepath.c_str())) {
return false;
}
depsgraph_create(eval_mode);
return true;
}
};
const std::string all_objects_file = "io_tests/blend_scene/all_objects.blend";
const std::string all_curve_objects_file = "io_tests/blend_scene/all_curves.blend";
TEST_F(obj_exporter_test, filter_objects_curves_as_mesh)
{
OBJExportParamsDefault _export;
if (!load_file_and_depsgraph(all_objects_file)) {
ADD_FAILURE();
return;
}
auto [objmeshes, objcurves]{filter_supported_objects(depsgraph, _export.params)};
EXPECT_EQ(objmeshes.size(), 17);
EXPECT_EQ(objcurves.size(), 0);
}
TEST_F(obj_exporter_test, filter_objects_curves_as_nurbs)
{
OBJExportParamsDefault _export;
if (!load_file_and_depsgraph(all_objects_file)) {
ADD_FAILURE();
return;
}
_export.params.export_curves_as_nurbs = true;
auto [objmeshes, objcurves]{filter_supported_objects(depsgraph, _export.params)};
EXPECT_EQ(objmeshes.size(), 16);
EXPECT_EQ(objcurves.size(), 2);
}
TEST_F(obj_exporter_test, filter_objects_selected)
{
OBJExportParamsDefault _export;
if (!load_file_and_depsgraph(all_objects_file)) {
ADD_FAILURE();
return;
}
_export.params.export_selected_objects = true;
_export.params.export_curves_as_nurbs = true;
auto [objmeshes, objcurves]{filter_supported_objects(depsgraph, _export.params)};
EXPECT_EQ(objmeshes.size(), 1);
EXPECT_EQ(objcurves.size(), 0);
}
TEST(obj_exporter_utils, append_negative_frame_to_filename)
{
const char path_original[FILE_MAX] = "/my_file.obj";
const char path_truth[FILE_MAX] = "/my_file-123.obj";
const int frame = -123;
char path_with_frame[FILE_MAX] = {0};
const bool ok = append_frame_to_filename(path_original, frame, path_with_frame);
EXPECT_TRUE(ok);
EXPECT_EQ_ARRAY(path_with_frame, path_truth, BLI_strlen_utf8(path_truth));
}
TEST(obj_exporter_utils, append_positive_frame_to_filename)
{
const char path_original[FILE_MAX] = "/my_file.obj";
const char path_truth[FILE_MAX] = "/my_file123.obj";
const int frame = 123;
char path_with_frame[FILE_MAX] = {0};
const bool ok = append_frame_to_filename(path_original, frame, path_with_frame);
EXPECT_TRUE(ok);
EXPECT_EQ_ARRAY(path_with_frame, path_truth, BLI_strlen_utf8(path_truth));
}
TEST_F(obj_exporter_test, curve_nurbs_points)
{
if (!load_file_and_depsgraph(all_curve_objects_file)) {
ADD_FAILURE();
return;
}
OBJExportParamsDefault _export;
_export.params.export_curves_as_nurbs = true;
auto [objmeshes_unused, objcurves]{filter_supported_objects(depsgraph, _export.params)};
for (auto &objcurve : objcurves) {
if (all_nurbs_truth.count(objcurve->get_curve_name()) != 1) {
ADD_FAILURE();
return;
}
const NurbsObject *const nurbs_truth = all_nurbs_truth.at(objcurve->get_curve_name()).get();
EXPECT_EQ(objcurve->total_splines(), nurbs_truth->total_splines());
for (int spline_index : IndexRange(objcurve->total_splines())) {
EXPECT_EQ(objcurve->total_spline_vertices(spline_index),
nurbs_truth->total_spline_vertices(spline_index));
EXPECT_EQ(objcurve->get_nurbs_degree(spline_index),
nurbs_truth->get_nurbs_degree(spline_index));
EXPECT_EQ(objcurve->total_spline_control_points(spline_index),
nurbs_truth->total_spline_control_points(spline_index));
}
}
}
TEST_F(obj_exporter_test, curve_coordinates)
{
if (!load_file_and_depsgraph(all_curve_objects_file)) {
ADD_FAILURE();
return;
}
OBJExportParamsDefault _export;
_export.params.export_curves_as_nurbs = true;
auto [objmeshes_unused, objcurves]{filter_supported_objects(depsgraph, _export.params)};
for (auto &objcurve : objcurves) {
if (all_nurbs_truth.count(objcurve->get_curve_name()) != 1) {
ADD_FAILURE();
return;
}
const NurbsObject *const nurbs_truth = all_nurbs_truth.at(objcurve->get_curve_name()).get();
EXPECT_EQ(objcurve->total_splines(), nurbs_truth->total_splines());
for (int spline_index : IndexRange(objcurve->total_splines())) {
for (int vertex_index : IndexRange(objcurve->total_spline_vertices(spline_index))) {
EXPECT_V3_NEAR(objcurve->vertex_coordinates(
spline_index, vertex_index, _export.params.scaling_factor),
nurbs_truth->vertex_coordinates(spline_index, vertex_index),
0.000001f);
}
}
}
}
static std::unique_ptr<OBJWriter> init_writer(const OBJExportParams &params,
const std::string out_filepath)
{
try {
auto writer = std::make_unique<OBJWriter>(out_filepath.c_str(), params);
return writer;
}
catch (const std::system_error &ex) {
std::cerr << ex.code().category().name() << ": " << ex.what() << ": " << ex.code().message()
<< std::endl;
return nullptr;
}
}
/* The following is relative to BKE_tempdir_base. */
const char *const temp_file_path = "output.OBJ";
static std::string read_temp_file_in_string(const std::string &file_path)
{
std::ifstream temp_stream(file_path);
std::ostringstream input_ss;
input_ss << temp_stream.rdbuf();
return input_ss.str();
}
TEST(obj_exporter_writer, header)
{
/* Because testing doesn't fully initialize Blender, we need the following. */
BKE_tempdir_init(NULL);
std::string out_file_path = blender::tests::flags_test_release_dir() + "/" + temp_file_path;
{
OBJExportParamsDefault _export;
std::unique_ptr<OBJWriter> writer = init_writer(_export.params, out_file_path);
if (!writer) {
ADD_FAILURE();
return;
}
writer->write_header();
}
const std::string result = read_temp_file_in_string(out_file_path);
using namespace std::string_literals;
ASSERT_EQ(result, "# Blender "s + BKE_blender_version_string() + "\n" + "# www.blender.org\n");
BLI_delete(out_file_path.c_str(), false, false);
}
TEST(obj_exporter_writer, mtllib)
{
std::string out_file_path = blender::tests::flags_test_release_dir() + "/" + temp_file_path;
{
OBJExportParamsDefault _export;
std::unique_ptr<OBJWriter> writer = init_writer(_export.params, out_file_path);
if (!writer) {
ADD_FAILURE();
return;
}
writer->write_mtllib_name("/Users/blah.mtl");
writer->write_mtllib_name("\\C:\\blah.mtl");
}
const std::string result = read_temp_file_in_string(out_file_path);
ASSERT_EQ(result, "mtllib blah.mtl\nmtllib blah.mtl\n");
}
/* Return true if string #a and string #b are equal after their first newline. */
static bool strings_equal_after_first_lines(const std::string &a, const std::string &b)
{
/* If `dbg_level > 0` then a failing test will print context around the first mismatch. */
const bool dbg_level = 0;
const size_t a_len = a.size();
const size_t b_len = b.size();
const size_t a_next = a.find_first_of('\n');
const size_t b_next = b.find_first_of('\n');
if (a_next == std::string::npos || b_next == std::string::npos) {
if (dbg_level > 0) {
std::cout << "Couldn't find newline in one of args\n";
}
return false;
}
if (dbg_level > 0) {
if (a.compare(a_next, a_len - a_next, b, b_next, b_len - b_next) != 0) {
for (int i = 0; i < a_len - a_next && i < b_len - b_next; ++i) {
if (a[a_next + i] != b[b_next + i]) {
std::cout << "Difference found at pos " << a_next + i << " of a\n";
std::cout << "a: " << a.substr(a_next + i, 100) << " ...\n";
std::cout << "b: " << b.substr(b_next + i, 100) << " ... \n";
return false;
}
}
}
else {
return true;
}
}
return a.compare(a_next, a_len - a_next, b, b_next, b_len - b_next) == 0;
}
/* From here on, tests are whole file tests, testing for golden output. */
class obj_exporter_regression_test : public obj_exporter_test {
public:
/**
* Export the given blend file with the given parameters and
* test to see if it matches a golden file (ignoring any difference in Blender version number).
* \param blendfile: input, relative to "tests" directory.
* \param golden_obj: expected output, relative to "tests" directory.
* \param params: the parameters to be used for export.
*/
void compare_obj_export_to_golden(const std::string &blendfile,
const std::string &golden_obj,
const std::string &golden_mtl,
OBJExportParams &params)
{
if (!load_file_and_depsgraph(blendfile)) {
return;
}
/* Because testing doesn't fully initialize Blender, we need the following. */
BKE_tempdir_init(NULL);
std::string tempdir = std::string(BKE_tempdir_base());
std::string out_file_path = tempdir + BLI_path_basename(golden_obj.c_str());
strncpy(params.filepath, out_file_path.c_str(), FILE_MAX);
params.blen_filepath = blendfile.c_str();
export_frame(depsgraph, params, out_file_path.c_str());
std::string output_str = read_temp_file_in_string(out_file_path);
std::string golden_file_path = blender::tests::flags_test_asset_dir() + "/" + golden_obj;
std::string golden_str = read_temp_file_in_string(golden_file_path);
ASSERT_TRUE(strings_equal_after_first_lines(output_str, golden_str));
BLI_delete(out_file_path.c_str(), false, false);
if (!golden_mtl.empty()) {
std::string out_mtl_file_path = tempdir + BLI_path_basename(golden_mtl.c_str());
std::string output_mtl_str = read_temp_file_in_string(out_mtl_file_path);
std::string golden_mtl_file_path = blender::tests::flags_test_asset_dir() + "/" + golden_mtl;
std::string golden_mtl_str = read_temp_file_in_string(golden_mtl_file_path);
ASSERT_TRUE(strings_equal_after_first_lines(output_mtl_str, golden_mtl_str));
BLI_delete(out_mtl_file_path.c_str(), false, false);
}
}
};
TEST_F(obj_exporter_regression_test, all_tris)
{
OBJExportParamsDefault _export;
compare_obj_export_to_golden("io_tests/blend_geometry/all_tris.blend",
"io_tests/obj/all_tris.obj",
"io_tests/obj/all_tris.mtl",
_export.params);
}
TEST_F(obj_exporter_regression_test, all_quads)
{
OBJExportParamsDefault _export;
_export.params.scaling_factor = 2.0f;
_export.params.export_materials = false;
compare_obj_export_to_golden(
"io_tests/blend_geometry/all_quads.blend", "io_tests/obj/all_quads.obj", "", _export.params);
}
TEST_F(obj_exporter_regression_test, fgons)
{
OBJExportParamsDefault _export;
_export.params.forward_axis = OBJ_AXIS_Y_FORWARD;
_export.params.up_axis = OBJ_AXIS_Z_UP;
_export.params.export_materials = false;
compare_obj_export_to_golden(
"io_tests/blend_geometry/fgons.blend", "io_tests/obj/fgons.obj", "", _export.params);
}
TEST_F(obj_exporter_regression_test, edges)
{
OBJExportParamsDefault _export;
_export.params.forward_axis = OBJ_AXIS_Y_FORWARD;
_export.params.up_axis = OBJ_AXIS_Z_UP;
_export.params.export_materials = false;
compare_obj_export_to_golden(
"io_tests/blend_geometry/edges.blend", "io_tests/obj/edges.obj", "", _export.params);
}
TEST_F(obj_exporter_regression_test, vertices)
{
OBJExportParamsDefault _export;
_export.params.forward_axis = OBJ_AXIS_Y_FORWARD;
_export.params.up_axis = OBJ_AXIS_Z_UP;
_export.params.export_materials = false;
compare_obj_export_to_golden(
"io_tests/blend_geometry/vertices.blend", "io_tests/obj/vertices.obj", "", _export.params);
}
TEST_F(obj_exporter_regression_test, nurbs_as_nurbs)
{
OBJExportParamsDefault _export;
_export.params.forward_axis = OBJ_AXIS_Y_FORWARD;
_export.params.up_axis = OBJ_AXIS_Z_UP;
_export.params.export_materials = false;
_export.params.export_curves_as_nurbs = true;
compare_obj_export_to_golden(
"io_tests/blend_geometry/nurbs.blend", "io_tests/obj/nurbs.obj", "", _export.params);
}
TEST_F(obj_exporter_regression_test, nurbs_as_mesh)
{
OBJExportParamsDefault _export;
_export.params.forward_axis = OBJ_AXIS_Y_FORWARD;
_export.params.up_axis = OBJ_AXIS_Z_UP;
_export.params.export_materials = false;
_export.params.export_curves_as_nurbs = false;
compare_obj_export_to_golden(
"io_tests/blend_geometry/nurbs.blend", "io_tests/obj/nurbs_mesh.obj", "", _export.params);
}
TEST_F(obj_exporter_regression_test, cube_all_data_triangulated)
{
OBJExportParamsDefault _export;
_export.params.forward_axis = OBJ_AXIS_Y_FORWARD;
_export.params.up_axis = OBJ_AXIS_Z_UP;
_export.params.export_materials = false;
_export.params.export_triangulated_mesh = true;
compare_obj_export_to_golden("io_tests/blend_geometry/cube_all_data.blend",
"io_tests/obj/cube_all_data_triangulated.obj",
"",
_export.params);
}
TEST_F(obj_exporter_regression_test, suzanne_all_data)
{
OBJExportParamsDefault _export;
_export.params.forward_axis = OBJ_AXIS_Y_FORWARD;
_export.params.up_axis = OBJ_AXIS_Z_UP;
_export.params.export_materials = false;
_export.params.export_smooth_groups = true;
compare_obj_export_to_golden("io_tests/blend_geometry/suzanne_all_data.blend",
"io_tests/obj/suzanne_all_data.obj",
"",
_export.params);
}
TEST_F(obj_exporter_regression_test, all_objects)
{
OBJExportParamsDefault _export;
_export.params.forward_axis = OBJ_AXIS_Y_FORWARD;
_export.params.up_axis = OBJ_AXIS_Z_UP;
_export.params.export_smooth_groups = true;
compare_obj_export_to_golden("io_tests/blend_scene/all_objects.blend",
"io_tests/obj/all_objects.obj",
"io_tests/obj/all_objects.mtl",
_export.params);
}
} // namespace blender::io::obj

View File

@ -0,0 +1,149 @@
/* Apache License, Version 2.0 */
/**
* This file contains default values for several items like
* vertex coordinates, export parameters, MTL values etc.
*/
#pragma once
#include <array>
#include <gtest/gtest.h>
#include <string>
#include <vector>
#include "IO_wavefront_obj.h"
namespace blender::io::obj {
using array_float_3 = std::array<float, 3>;
/**
* This matches #OBJCurve's member functions, except that all the numbers and names are known
* constants. Used to store expected values of NURBS Curve sobjects.
*/
class NurbsObject {
private:
std::string nurbs_name_;
/* The indices in these vectors are spline indices. */
std::vector<std::vector<array_float_3>> coordinates_;
std::vector<int> degrees_;
std::vector<int> control_points_;
public:
NurbsObject(const std::string nurbs_name,
const std::vector<std::vector<array_float_3>> coordinates,
const std::vector<int> degrees,
const std::vector<int> control_points)
: nurbs_name_(nurbs_name),
coordinates_(coordinates),
degrees_(degrees),
control_points_(control_points)
{
}
int total_splines() const
{
return coordinates_.size();
}
int total_spline_vertices(const int spline_index) const
{
if (spline_index >= coordinates_.size()) {
ADD_FAILURE();
return 0;
}
return coordinates_[spline_index].size();
}
const float *vertex_coordinates(const int spline_index, const int vertex_index) const
{
return coordinates_[spline_index][vertex_index].data();
}
int get_nurbs_degree(const int spline_index) const
{
return degrees_[spline_index];
}
int total_spline_control_points(const int spline_index) const
{
return control_points_[spline_index];
}
};
struct OBJExportParamsDefault {
OBJExportParams params;
OBJExportParamsDefault()
{
params.filepath[0] = '\0';
params.blen_filepath = "";
params.export_animation = false;
params.start_frame = 0;
params.end_frame = 1;
params.forward_axis = OBJ_AXIS_NEGATIVE_Z_FORWARD;
params.up_axis = OBJ_AXIS_Y_UP;
params.scaling_factor = 1.f;
params.export_eval_mode = DAG_EVAL_VIEWPORT;
params.export_selected_objects = false;
params.export_uv = true;
params.export_normals = true;
params.export_materials = true;
params.export_triangulated_mesh = false;
params.export_curves_as_nurbs = false;
params.export_object_groups = false;
params.export_material_groups = false;
params.export_vertex_groups = false;
params.export_smooth_groups = true;
params.smooth_groups_bitflags = false;
}
};
const std::vector<std::vector<array_float_3>> coordinates_NurbsCurve{
{{6.94742, 0.000000, 0.000000},
{7.44742, 0.000000, -1.000000},
{9.44742, 0.000000, -1.000000},
{9.94742, 0.000000, 0.000000}}};
const std::vector<std::vector<array_float_3>> coordinates_NurbsCircle{
{{11.463165, 0.000000, 1.000000},
{10.463165, 0.000000, 1.000000},
{10.463165, 0.000000, 0.000000},
{10.463165, 0.000000, -1.000000},
{11.463165, 0.000000, -1.000000},
{12.463165, 0.000000, -1.000000},
{12.463165, 0.000000, 0.000000},
{12.463165, 0.000000, 1.000000}}};
const std::vector<std::vector<array_float_3>> coordinates_NurbsPathCurve{
{{13.690557, 0.000000, 0.000000},
{14.690557, 0.000000, 0.000000},
{15.690557, 0.000000, 0.000000},
{16.690557, 0.000000, 0.000000},
{17.690557, 0.000000, 0.000000}},
{{14.192808, 0.000000, 0.000000},
{14.692808, 0.000000, -1.000000},
{16.692808, 0.000000, -1.000000},
{17.192808, 0.000000, 0.000000}}};
const std::map<std::string, std::unique_ptr<NurbsObject>> all_nurbs_truth = []() {
std::map<std::string, std::unique_ptr<NurbsObject>> all_nurbs;
all_nurbs.emplace(
"NurbsCurve",
/* Name, coordinates, degrees of splines, control points of splines. */
std::make_unique<NurbsObject>(
"NurbsCurve", coordinates_NurbsCurve, std::vector<int>{3}, std::vector<int>{4}));
all_nurbs.emplace(
"NurbsCircle",
std::make_unique<NurbsObject>(
"NurbsCircle", coordinates_NurbsCircle, std::vector<int>{3}, std::vector<int>{11}));
/* This is actually an Object containing a NurbsPath and a NurbsCurve spline. */
all_nurbs.emplace("NurbsPathCurve",
std::make_unique<NurbsObject>("NurbsPathCurve",
coordinates_NurbsPathCurve,
std::vector<int>{3, 3},
std::vector<int>{5, 4}));
return all_nurbs;
}();
} // namespace blender::io::obj

View File

@ -584,6 +584,8 @@ static void rna_def_cloth_sim_settings(BlenderRNA *brna)
RNA_def_struct_sdna(srna, "ClothSimSettings");
RNA_def_struct_path_func(srna, "rna_ClothSettings_path");
RNA_define_lib_overridable(true);
/* goal */
prop = RNA_def_property(srna, "goal_min", PROP_FLOAT, PROP_FACTOR);
@ -659,6 +661,7 @@ static void rna_def_cloth_sim_settings(BlenderRNA *brna)
"rna_ClothSettings_mass_vgroup_get",
"rna_ClothSettings_mass_vgroup_length",
"rna_ClothSettings_mass_vgroup_set");
RNA_def_property_override_clear_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_ui_text(prop, "Mass Vertex Group", "Vertex Group for pinning of vertices");
RNA_def_property_update(prop, 0, "rna_cloth_pinning_changed");
@ -707,6 +710,7 @@ static void rna_def_cloth_sim_settings(BlenderRNA *brna)
"rna_ClothSettings_shrink_vgroup_get",
"rna_ClothSettings_shrink_vgroup_length",
"rna_ClothSettings_shrink_vgroup_set");
RNA_def_property_override_clear_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_ui_text(prop, "Shrink Vertex Group", "Vertex Group for shrinking cloth");
RNA_def_property_update(prop, 0, "rna_cloth_update");
@ -810,6 +814,7 @@ static void rna_def_cloth_sim_settings(BlenderRNA *brna)
"rna_ClothSettings_struct_vgroup_get",
"rna_ClothSettings_struct_vgroup_length",
"rna_ClothSettings_struct_vgroup_set");
RNA_def_property_override_clear_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_ui_text(prop,
"Structural Stiffness Vertex Group",
"Vertex group for fine control over structural stiffness");
@ -820,6 +825,7 @@ static void rna_def_cloth_sim_settings(BlenderRNA *brna)
"rna_ClothSettings_shear_vgroup_get",
"rna_ClothSettings_shear_vgroup_length",
"rna_ClothSettings_shear_vgroup_set");
RNA_def_property_override_clear_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_ui_text(
prop, "Shear Stiffness Vertex Group", "Vertex group for fine control over shear stiffness");
RNA_def_property_update(prop, 0, "rna_cloth_update");
@ -856,6 +862,7 @@ static void rna_def_cloth_sim_settings(BlenderRNA *brna)
"rna_ClothSettings_bend_vgroup_get",
"rna_ClothSettings_bend_vgroup_length",
"rna_ClothSettings_bend_vgroup_set");
RNA_def_property_override_clear_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_ui_text(prop,
"Bending Stiffness Vertex Group",
"Vertex group for fine control over bending stiffness");
@ -874,6 +881,7 @@ static void rna_def_cloth_sim_settings(BlenderRNA *brna)
"rna_ClothSettings_rest_shape_key_set",
NULL,
NULL);
RNA_def_property_override_clear_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_ui_text(
prop, "Rest Shape Key", "Shape key to use the rest spring lengths from");
RNA_def_property_update(prop, 0, "rna_cloth_update");
@ -976,6 +984,7 @@ static void rna_def_cloth_sim_settings(BlenderRNA *brna)
"rna_ClothSettings_internal_vgroup_get",
"rna_ClothSettings_internal_vgroup_length",
"rna_ClothSettings_internal_vgroup_set");
RNA_def_property_override_clear_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_ui_text(prop,
"Internal Springs Vertex Group",
"Vertex group for fine control over the internal spring stiffness");
@ -1044,6 +1053,7 @@ static void rna_def_cloth_sim_settings(BlenderRNA *brna)
"rna_ClothSettings_pressure_vgroup_get",
"rna_ClothSettings_pressure_vgroup_length",
"rna_ClothSettings_pressure_vgroup_set");
RNA_def_property_override_clear_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_ui_text(
prop,
"Pressure Vertex Group",
@ -1082,6 +1092,8 @@ static void rna_def_cloth_sim_settings(BlenderRNA *brna)
RNA_def_property_ui_text(
prop, "Maximum Spring Extension", "Maximum extension before spring gets cut");
# endif
RNA_define_lib_overridable(false);
}
static void rna_def_cloth_collision_settings(BlenderRNA *brna)
@ -1097,6 +1109,8 @@ static void rna_def_cloth_collision_settings(BlenderRNA *brna)
RNA_def_struct_sdna(srna, "ClothCollSettings");
RNA_def_struct_path_func(srna, "rna_ClothCollisionSettings_path");
RNA_define_lib_overridable(true);
/* general collision */
prop = RNA_def_property(srna, "use_collision", PROP_BOOLEAN, PROP_NONE);
@ -1169,7 +1183,6 @@ static void rna_def_cloth_collision_settings(BlenderRNA *brna)
prop = RNA_def_property(srna, "collection", PROP_POINTER, PROP_NONE);
RNA_def_property_pointer_sdna(prop, NULL, "group");
RNA_def_property_flag(prop, PROP_EDITABLE);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_ui_text(prop, "Collision Collection", "Limit colliders to this Collection");
RNA_def_property_update(prop, 0, "rna_cloth_dependency_update");
@ -1178,6 +1191,7 @@ static void rna_def_cloth_collision_settings(BlenderRNA *brna)
"rna_CollSettings_selfcol_vgroup_get",
"rna_CollSettings_selfcol_vgroup_length",
"rna_CollSettings_selfcol_vgroup_set");
RNA_def_property_override_clear_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_ui_text(
prop,
"Selfcollision Vertex Group",
@ -1189,6 +1203,7 @@ static void rna_def_cloth_collision_settings(BlenderRNA *brna)
"rna_CollSettings_objcol_vgroup_get",
"rna_CollSettings_objcol_vgroup_length",
"rna_CollSettings_objcol_vgroup_set");
RNA_def_property_override_clear_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_ui_text(
prop,
"Collision Vertex Group",
@ -1203,6 +1218,8 @@ static void rna_def_cloth_collision_settings(BlenderRNA *brna)
"Impulse Clamping",
"Clamp collision impulses to avoid instability (0.0 to disable clamping)");
RNA_def_property_update(prop, 0, "rna_cloth_update");
RNA_define_lib_overridable(false);
}
void RNA_def_cloth(BlenderRNA *brna)

View File

@ -589,6 +589,7 @@ static void rna_def_canvas_surface(BlenderRNA *brna)
prop = RNA_def_property(srna, "effector_weights", PROP_POINTER, PROP_NONE);
RNA_def_property_struct_type(prop, "EffectorWeights");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_ui_text(prop, "Effector Weights", "");
prop = RNA_def_property(srna, "drip_velocity", PROP_FLOAT, PROP_NONE);

View File

@ -1461,6 +1461,7 @@ static void rna_def_fluid_domain_settings(BlenderRNA *brna)
prop = RNA_def_property(srna, "effector_weights", PROP_POINTER, PROP_NONE);
RNA_def_property_struct_type(prop, "EffectorWeights");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_ui_text(prop, "Effector Weights", "");
/* object collections */

View File

@ -1953,7 +1953,7 @@ static bNodeType *rna_Node_register_base(Main *bmain,
/* setup dummy node & node type to store static properties in */
memset(&dummynt, 0, sizeof(bNodeType));
/* this does some additional initialization of default values */
node_type_base_custom(&dummynt, identifier, "", 0, 0);
node_type_base_custom(&dummynt, identifier, "", 0);
memset(&dummynode, 0, sizeof(bNode));
dummynode.typeinfo = &dummynt;

View File

@ -1128,6 +1128,8 @@ static void rna_def_collision(BlenderRNA *brna)
RNA_def_struct_ui_text(
srna, "Collision Settings", "Collision settings for object in physics simulation");
RNA_define_lib_overridable(true);
prop = RNA_def_property(srna, "use", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "deflect", 1);
RNA_def_property_ui_text(
@ -1230,6 +1232,8 @@ static void rna_def_collision(BlenderRNA *brna)
"Cloth collision impulses act in the direction of the collider normals "
"(more reliable in some cases)");
RNA_def_property_update(prop, 0, "rna_CollisionSettings_update");
RNA_define_lib_overridable(false);
}
static void rna_def_effector_weight(BlenderRNA *brna)
@ -1243,6 +1247,8 @@ static void rna_def_effector_weight(BlenderRNA *brna)
RNA_def_struct_ui_text(srna, "Effector Weights", "Effector weights for physics simulation");
RNA_def_struct_ui_icon(srna, ICON_PHYSICS);
RNA_define_lib_overridable(true);
/* Flags */
prop = RNA_def_property(srna, "apply_to_hair_growing", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", EFF_WEIGHT_DO_HAIR);
@ -1362,6 +1368,8 @@ static void rna_def_effector_weight(BlenderRNA *brna)
RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.1, 3);
RNA_def_property_ui_text(prop, "Fluid Flow", "Fluid Flow effector weight");
RNA_def_property_update(prop, 0, "rna_EffectorWeight_update");
RNA_define_lib_overridable(false);
}
static void rna_def_field(BlenderRNA *brna)
@ -1471,6 +1479,8 @@ static void rna_def_field(BlenderRNA *brna)
srna, "Field Settings", "Field settings for an object in physics simulation");
RNA_def_struct_ui_icon(srna, ICON_PHYSICS);
RNA_define_lib_overridable(true);
/* Enums */
prop = RNA_def_property(srna, "type", PROP_ENUM, PROP_NONE);
@ -1514,34 +1524,34 @@ static void rna_def_field(BlenderRNA *brna)
prop = RNA_def_property(srna, "strength", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, NULL, "f_strength");
RNA_def_property_range(prop, -FLT_MAX, FLT_MAX);
RNA_def_property_ui_range(prop, -FLT_MAX, FLT_MAX, 10, 3);
RNA_def_property_ui_text(prop, "Strength", "Strength of force field");
RNA_def_property_update(prop, 0, "rna_FieldSettings_update");
/* different ui range to above */
prop = RNA_def_property(srna, "linear_drag", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, NULL, "f_strength");
RNA_def_property_range(prop, -2.0f, 2.0f);
RNA_def_property_ui_range(prop, -2.0f, 2.0f, 10, 3);
RNA_def_property_ui_text(prop, "Linear Drag", "Drag component proportional to velocity");
RNA_def_property_update(prop, 0, "rna_FieldSettings_update");
prop = RNA_def_property(srna, "harmonic_damping", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, NULL, "f_damp");
RNA_def_property_range(prop, 0.0f, 10.0f);
RNA_def_property_ui_range(prop, 0.0f, 10.0f, 10, 3);
RNA_def_property_ui_text(prop, "Harmonic Damping", "Damping of the harmonic force");
RNA_def_property_update(prop, 0, "rna_FieldSettings_update");
/* different ui range to above */
prop = RNA_def_property(srna, "quadratic_drag", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, NULL, "f_damp");
RNA_def_property_range(prop, -2.0f, 2.0f);
RNA_def_property_ui_range(prop, -2.0f, 2.0f, 10, 3);
RNA_def_property_ui_text(
prop, "Quadratic Drag", "Drag component proportional to the square of velocity");
RNA_def_property_update(prop, 0, "rna_FieldSettings_update");
prop = RNA_def_property(srna, "flow", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, NULL, "f_flow");
RNA_def_property_range(prop, 0.0f, 10.0f);
RNA_def_property_ui_range(prop, 0.0f, 10.0f, 10, 3);
RNA_def_property_ui_text(prop, "Flow", "Convert effector force into air flow velocity");
RNA_def_property_update(prop, 0, "rna_FieldSettings_update");
@ -1557,7 +1567,7 @@ static void rna_def_field(BlenderRNA *brna)
/* different ui range to above */
prop = RNA_def_property(srna, "inflow", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, NULL, "f_flow");
RNA_def_property_range(prop, -10.0f, 10.0f);
RNA_def_property_ui_range(prop, -10.0f, 10.0f, 10, 3);
RNA_def_property_ui_text(prop, "Inflow", "Inwards component of the vortex force");
RNA_def_property_update(prop, 0, "rna_FieldSettings_update");
@ -1570,7 +1580,8 @@ static void rna_def_field(BlenderRNA *brna)
prop = RNA_def_property(srna, "rest_length", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, NULL, "f_size");
RNA_def_property_range(prop, 0.0f, 1000.0f);
RNA_def_property_range(prop, 0.0f, FLT_MAX);
RNA_def_property_ui_range(prop, 0.0f, 1000.0f, 10, 3);
RNA_def_property_ui_text(prop, "Rest Length", "Rest length of the harmonic force");
RNA_def_property_update(prop, 0, "rna_FieldSettings_update");
@ -1728,7 +1739,7 @@ static void rna_def_field(BlenderRNA *brna)
prop = RNA_def_property(srna, "guide_minimum", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, NULL, "f_strength");
RNA_def_property_range(prop, 0.0f, 1000.0f);
RNA_def_property_ui_range(prop, 0.0f, 1000.0f, 10, 3);
RNA_def_property_ui_text(
prop, "Minimum Distance", "The distance from which particles are affected fully");
RNA_def_property_update(prop, 0, "rna_FieldSettings_update");
@ -1799,6 +1810,8 @@ static void rna_def_field(BlenderRNA *brna)
/* Variables used for Curve Guide, already wrapped, used for other fields too */
/* falloff_power, use_max_distance, maximum_distance */
RNA_define_lib_overridable(false);
}
static void rna_def_softbody(BlenderRNA *brna)
@ -2134,6 +2147,7 @@ static void rna_def_softbody(BlenderRNA *brna)
RNA_def_property_pointer_sdna(prop, NULL, "effector_weights");
RNA_def_property_struct_type(prop, "EffectorWeights");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_ui_text(prop, "Effector Weights", "");
}

View File

@ -3498,6 +3498,7 @@ static void rna_def_particle_settings(BlenderRNA *brna)
prop = RNA_def_property(srna, "effector_weights", PROP_POINTER, PROP_NONE);
RNA_def_property_struct_type(prop, "EffectorWeights");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_ui_text(prop, "Effector Weights", "");
/* animation here? */
@ -3507,12 +3508,14 @@ static void rna_def_particle_settings(BlenderRNA *brna)
RNA_def_property_pointer_sdna(prop, NULL, "pd");
RNA_def_property_struct_type(prop, "FieldSettings");
RNA_def_property_pointer_funcs(prop, "rna_Particle_field1_get", NULL, NULL, NULL);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_ui_text(prop, "Force Field 1", "");
prop = RNA_def_property(srna, "force_field_2", PROP_POINTER, PROP_NONE);
RNA_def_property_pointer_sdna(prop, NULL, "pd2");
RNA_def_property_struct_type(prop, "FieldSettings");
RNA_def_property_pointer_funcs(prop, "rna_Particle_field2_get", NULL, NULL, NULL);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_ui_text(prop, "Force Field 2", "");
/* twist */

View File

@ -957,6 +957,7 @@ static void rna_def_rigidbody_world(BlenderRNA *brna)
prop = RNA_def_property(srna, "effector_weights", PROP_POINTER, PROP_NONE);
RNA_def_property_struct_type(prop, "EffectorWeights");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_ui_text(prop, "Effector Weights", "");
/* Sweep test */

View File

@ -702,7 +702,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *UNUSED(ctx)
Object *armature_ob = mmd->ob_arm;
/* Return input mesh if there is no armature with bones. */
if (ELEM(NULL, armature_ob, armature_ob->pose)) {
if (ELEM(nullptr, armature_ob, armature_ob->pose)) {
return mesh;
}

View File

@ -106,6 +106,7 @@ void register_node_type_geo_input_id(void);
void register_node_type_geo_input_index(void);
void register_node_type_geo_input_material_index(void);
void register_node_type_geo_input_material(void);
void register_node_type_geo_input_mesh_edge_angle(void);
void register_node_type_geo_input_mesh_edge_neighbors(void);
void register_node_type_geo_input_mesh_edge_vertices(void);
void register_node_type_geo_input_mesh_face_area(void);

View File

@ -359,6 +359,7 @@ DefNode(GeometryNode, GEO_NODE_INPUT_ID, 0, "INPUT_ID", InputID, "ID", "")
DefNode(GeometryNode, GEO_NODE_INPUT_INDEX, 0, "INDEX", InputIndex, "Index", "")
DefNode(GeometryNode, GEO_NODE_INPUT_MATERIAL_INDEX, 0, "INPUT_MATERIAL_INDEX", InputMaterialIndex, "Material Index", "")
DefNode(GeometryNode, GEO_NODE_INPUT_MATERIAL, def_geo_input_material, "INPUT_MATERIAL", InputMaterial, "Material", "")
DefNode(GeometryNode, GEO_NODE_INPUT_MESH_EDGE_ANGLE, 0, "MESH_EDGE_ANGLE", InputMeshEdgeAngle, "Edge Angle", "")
DefNode(GeometryNode, GEO_NODE_INPUT_MESH_EDGE_NEIGHBORS, 0, "MESH_EDGE_NEIGHBORS", InputMeshEdgeNeighbors, "Edge Neighbors", "")
DefNode(GeometryNode, GEO_NODE_INPUT_MESH_EDGE_VERTICES, 0, "MESH_EDGE_VERTICES", InputMeshEdgeVertices, "Edge Vertices", "")
DefNode(GeometryNode, GEO_NODE_INPUT_MESH_FACE_AREA, 0, "MESH_FACE_AREA", InputMeshFaceArea, "Face Area", "")

View File

@ -47,9 +47,9 @@ void cmp_node_update_default(bNodeTree *UNUSED(ntree), bNode *node)
node->need_exec = 1;
}
void cmp_node_type_base(bNodeType *ntype, int type, const char *name, short nclass, short flag)
void cmp_node_type_base(bNodeType *ntype, int type, const char *name, short nclass)
{
node_type_base(ntype, type, name, nclass, flag);
node_type_base(ntype, type, name, nclass);
ntype->poll = cmp_node_poll_default;
ntype->updatefunc = cmp_node_update_default;

View File

@ -52,5 +52,4 @@ bool cmp_node_poll_default(struct bNodeType *ntype,
struct bNodeTree *ntree,
const char **r_disabled_hint);
void cmp_node_update_default(struct bNodeTree *ntree, struct bNode *node);
void cmp_node_type_base(
struct bNodeType *ntype, int type, const char *name, short nclass, short flag);
void cmp_node_type_base(struct bNodeType *ntype, int type, const char *name, short nclass);

View File

@ -58,7 +58,7 @@ void register_node_type_cmp_alphaover()
{
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_ALPHAOVER, "Alpha Over", NODE_CLASS_OP_COLOR, 0);
cmp_node_type_base(&ntype, CMP_NODE_ALPHAOVER, "Alpha Over", NODE_CLASS_OP_COLOR);
ntype.declare = blender::nodes::cmp_node_alphaover_declare;
ntype.draw_buttons = node_composit_buts_alphaover;
node_type_init(&ntype, node_alphaover_init);

View File

@ -66,10 +66,10 @@ void register_node_type_cmp_antialiasing()
{
static bNodeType ntype;
cmp_node_type_base(
&ntype, CMP_NODE_ANTIALIASING, "Anti-Aliasing", NODE_CLASS_OP_FILTER, NODE_PREVIEW);
cmp_node_type_base(&ntype, CMP_NODE_ANTIALIASING, "Anti-Aliasing", NODE_CLASS_OP_FILTER);
ntype.declare = blender::nodes::cmp_node_antialiasing_declare;
ntype.draw_buttons = node_composit_buts_antialiasing;
ntype.flag |= NODE_PREVIEW;
node_type_size(&ntype, 170, 140, 200);
node_type_init(&ntype, node_composit_init_antialiasing);
node_type_storage(

View File

@ -64,7 +64,7 @@ void register_node_type_cmp_bilateralblur()
{
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_BILATERALBLUR, "Bilateral Blur", NODE_CLASS_OP_FILTER, 0);
cmp_node_type_base(&ntype, CMP_NODE_BILATERALBLUR, "Bilateral Blur", NODE_CLASS_OP_FILTER);
ntype.declare = blender::nodes::cmp_node_bilateralblur_declare;
ntype.draw_buttons = node_composit_buts_bilateralblur;
node_type_init(&ntype, node_composit_init_bilateralblur);

View File

@ -94,9 +94,10 @@ void register_node_type_cmp_blur()
{
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_BLUR, "Blur", NODE_CLASS_OP_FILTER, NODE_PREVIEW);
cmp_node_type_base(&ntype, CMP_NODE_BLUR, "Blur", NODE_CLASS_OP_FILTER);
ntype.declare = blender::nodes::cmp_node_blur_declare;
ntype.draw_buttons = node_composit_buts_blur;
ntype.flag |= NODE_PREVIEW;
node_type_init(&ntype, node_composit_init_blur);
node_type_storage(
&ntype, "NodeBlurData", node_free_standard_storage, node_copy_standard_storage);

View File

@ -60,7 +60,7 @@ void register_node_type_cmp_bokehblur()
{
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_BOKEHBLUR, "Bokeh Blur", NODE_CLASS_OP_FILTER, 0);
cmp_node_type_base(&ntype, CMP_NODE_BOKEHBLUR, "Bokeh Blur", NODE_CLASS_OP_FILTER);
ntype.declare = blender::nodes::cmp_node_bokehblur_declare;
ntype.draw_buttons = node_composit_buts_bokehblur;
node_type_init(&ntype, node_composit_init_bokehblur);

View File

@ -67,9 +67,10 @@ void register_node_type_cmp_bokehimage()
{
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_BOKEHIMAGE, "Bokeh Image", NODE_CLASS_INPUT, NODE_PREVIEW);
cmp_node_type_base(&ntype, CMP_NODE_BOKEHIMAGE, "Bokeh Image", NODE_CLASS_INPUT);
ntype.declare = blender::nodes::cmp_node_bokehimage_declare;
ntype.draw_buttons = node_composit_buts_bokehimage;
ntype.flag |= NODE_PREVIEW;
node_type_init(&ntype, node_composit_init_bokehimage);
node_type_storage(
&ntype, "NodeBokehImage", node_free_standard_storage, node_copy_standard_storage);

View File

@ -70,7 +70,7 @@ void register_node_type_cmp_boxmask()
{
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_MASK_BOX, "Box Mask", NODE_CLASS_MATTE, 0);
cmp_node_type_base(&ntype, CMP_NODE_MASK_BOX, "Box Mask", NODE_CLASS_MATTE);
ntype.declare = blender::nodes::cmp_node_boxmask_declare;
ntype.draw_buttons = node_composit_buts_boxmask;
node_type_init(&ntype, node_composit_init_boxmask);

View File

@ -56,7 +56,7 @@ void register_node_type_cmp_brightcontrast()
{
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_BRIGHTCONTRAST, "Bright/Contrast", NODE_CLASS_OP_COLOR, 0);
cmp_node_type_base(&ntype, CMP_NODE_BRIGHTCONTRAST, "Bright/Contrast", NODE_CLASS_OP_COLOR);
ntype.declare = blender::nodes::cmp_node_brightcontrast_declare;
ntype.draw_buttons = node_composit_buts_brightcontrast;
node_type_init(&ntype, node_composit_init_brightcontrast);

View File

@ -101,10 +101,10 @@ void register_node_type_cmp_channel_matte()
{
static bNodeType ntype;
cmp_node_type_base(
&ntype, CMP_NODE_CHANNEL_MATTE, "Channel Key", NODE_CLASS_MATTE, NODE_PREVIEW);
cmp_node_type_base(&ntype, CMP_NODE_CHANNEL_MATTE, "Channel Key", NODE_CLASS_MATTE);
ntype.declare = blender::nodes::cmp_node_channel_matte_declare;
ntype.draw_buttons = node_composit_buts_channel_matte;
ntype.flag |= NODE_PREVIEW;
node_type_init(&ntype, node_composit_init_channel_matte);
node_type_storage(&ntype, "NodeChroma", node_free_standard_storage, node_copy_standard_storage);

View File

@ -71,9 +71,10 @@ void register_node_type_cmp_chroma_matte()
{
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_CHROMA_MATTE, "Chroma Key", NODE_CLASS_MATTE, NODE_PREVIEW);
cmp_node_type_base(&ntype, CMP_NODE_CHROMA_MATTE, "Chroma Key", NODE_CLASS_MATTE);
ntype.declare = blender::nodes::cmp_node_chroma_matte_declare;
ntype.draw_buttons = node_composit_buts_chroma_matte;
ntype.flag |= NODE_PREVIEW;
node_type_init(&ntype, node_composit_init_chroma_matte);
node_type_storage(&ntype, "NodeChroma", node_free_standard_storage, node_copy_standard_storage);

View File

@ -72,9 +72,10 @@ void register_node_type_cmp_color_matte()
{
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_COLOR_MATTE, "Color Key", NODE_CLASS_MATTE, NODE_PREVIEW);
cmp_node_type_base(&ntype, CMP_NODE_COLOR_MATTE, "Color Key", NODE_CLASS_MATTE);
ntype.declare = blender::nodes::cmp_node_color_matte_declare;
ntype.draw_buttons = node_composit_buts_color_matte;
ntype.flag |= NODE_PREVIEW;
node_type_init(&ntype, node_composit_init_color_matte);
node_type_storage(&ntype, "NodeChroma", node_free_standard_storage, node_copy_standard_storage);

View File

@ -102,7 +102,7 @@ void register_node_type_cmp_color_spill()
{
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_COLOR_SPILL, "Color Spill", NODE_CLASS_MATTE, 0);
cmp_node_type_base(&ntype, CMP_NODE_COLOR_SPILL, "Color Spill", NODE_CLASS_MATTE);
ntype.declare = blender::nodes::cmp_node_color_spill_declare;
ntype.draw_buttons = node_composit_buts_color_spill;
node_type_init(&ntype, node_composit_init_color_spill);

View File

@ -161,7 +161,7 @@ void register_node_type_cmp_colorbalance()
{
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_COLORBALANCE, "Color Balance", NODE_CLASS_OP_COLOR, 0);
cmp_node_type_base(&ntype, CMP_NODE_COLORBALANCE, "Color Balance", NODE_CLASS_OP_COLOR);
ntype.declare = blender::nodes::cmp_node_colorbalance_declare;
ntype.draw_buttons = node_composit_buts_colorbalance;
ntype.draw_buttons_ex = node_composit_buts_colorbalance_ex;

View File

@ -288,7 +288,7 @@ void register_node_type_cmp_colorcorrection()
{
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_COLORCORRECTION, "Color Correction", NODE_CLASS_OP_COLOR, 0);
cmp_node_type_base(&ntype, CMP_NODE_COLORCORRECTION, "Color Correction", NODE_CLASS_OP_COLOR);
ntype.declare = blender::nodes::cmp_node_colorcorrection_declare;
ntype.draw_buttons = node_composit_buts_colorcorrection;
ntype.draw_buttons_ex = node_composit_buts_colorcorrection_ex;

View File

@ -38,7 +38,7 @@ void register_node_type_cmp_group()
/* NOTE: Cannot use sh_node_type_base for node group, because it would map the node type
* to the shared NODE_GROUP integer type id. */
node_type_base_custom(&ntype, "CompositorNodeGroup", "Group", NODE_CLASS_GROUP, 0);
node_type_base_custom(&ntype, "CompositorNodeGroup", "Group", NODE_CLASS_GROUP);
ntype.type = NODE_GROUP;
ntype.poll = cmp_node_poll_default;
ntype.poll_instance = node_group_poll_instance;

View File

@ -48,9 +48,10 @@ void register_node_type_cmp_composite()
{
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_COMPOSITE, "Composite", NODE_CLASS_OUTPUT, NODE_PREVIEW);
cmp_node_type_base(&ntype, CMP_NODE_COMPOSITE, "Composite", NODE_CLASS_OUTPUT);
ntype.declare = blender::nodes::cmp_node_composite_declare;
ntype.draw_buttons = node_composit_buts_composite;
ntype.flag |= NODE_PREVIEW;
ntype.no_muting = true;
nodeRegisterType(&ntype);

View File

@ -54,7 +54,7 @@ void register_node_type_cmp_cornerpin()
{
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_CORNERPIN, "Corner Pin", NODE_CLASS_DISTORT, 0);
cmp_node_type_base(&ntype, CMP_NODE_CORNERPIN, "Corner Pin", NODE_CLASS_DISTORT);
ntype.declare = blender::nodes::cmp_node_cornerpin_declare;
nodeRegisterType(&ntype);

View File

@ -76,7 +76,7 @@ void register_node_type_cmp_crop()
{
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_CROP, "Crop", NODE_CLASS_DISTORT, 0);
cmp_node_type_base(&ntype, CMP_NODE_CROP, "Crop", NODE_CLASS_DISTORT);
ntype.declare = blender::nodes::cmp_node_crop_declare;
ntype.draw_buttons = node_composit_buts_crop;
node_type_init(&ntype, node_composit_init_crop);

View File

@ -309,7 +309,7 @@ void register_node_type_cmp_cryptomatte()
{
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_CRYPTOMATTE, "Cryptomatte", NODE_CLASS_MATTE, 0);
cmp_node_type_base(&ntype, CMP_NODE_CRYPTOMATTE, "Cryptomatte", NODE_CLASS_MATTE);
node_type_socket_templates(&ntype, cmp_node_cryptomatte_in, cmp_node_cryptomatte_out);
node_type_size(&ntype, 240, 100, 700);
node_type_init(&ntype, node_init_cryptomatte);
@ -366,7 +366,7 @@ void register_node_type_cmp_cryptomatte_legacy()
{
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_CRYPTOMATTE_LEGACY, "Cryptomatte", NODE_CLASS_MATTE, 0);
cmp_node_type_base(&ntype, CMP_NODE_CRYPTOMATTE_LEGACY, "Cryptomatte", NODE_CLASS_MATTE);
node_type_socket_templates(&ntype, nullptr, cmp_node_cryptomatte_out);
node_type_init(&ntype, node_init_cryptomatte_legacy);
node_type_storage(&ntype, "NodeCryptomatte", node_free_cryptomatte, node_copy_cryptomatte);

View File

@ -49,7 +49,7 @@ void register_node_type_cmp_curve_time()
{
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_TIME, "Time", NODE_CLASS_INPUT, 0);
cmp_node_type_base(&ntype, CMP_NODE_TIME, "Time", NODE_CLASS_INPUT);
ntype.declare = blender::nodes::cmp_node_time_declare;
node_type_size(&ntype, 200, 140, 320);
node_type_init(&ntype, node_composit_init_curves_time);
@ -84,7 +84,7 @@ void register_node_type_cmp_curve_vec()
{
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_CURVE_VEC, "Vector Curves", NODE_CLASS_OP_VECTOR, 0);
cmp_node_type_base(&ntype, CMP_NODE_CURVE_VEC, "Vector Curves", NODE_CLASS_OP_VECTOR);
ntype.declare = blender::nodes::cmp_node_curve_vec_declare;
ntype.draw_buttons = node_buts_curvevec;
node_type_size(&ntype, 200, 140, 320);
@ -119,7 +119,7 @@ void register_node_type_cmp_curve_rgb()
{
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_CURVE_RGB, "RGB Curves", NODE_CLASS_OP_COLOR, 0);
cmp_node_type_base(&ntype, CMP_NODE_CURVE_RGB, "RGB Curves", NODE_CLASS_OP_COLOR);
ntype.declare = blender::nodes::cmp_node_rgbcurves_declare;
node_type_size(&ntype, 200, 140, 320);
node_type_init(&ntype, node_composit_init_curve_rgb);

View File

@ -103,7 +103,7 @@ void register_node_type_cmp_defocus()
{
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_DEFOCUS, "Defocus", NODE_CLASS_OP_FILTER, 0);
cmp_node_type_base(&ntype, CMP_NODE_DEFOCUS, "Defocus", NODE_CLASS_OP_FILTER);
ntype.declare = blender::nodes::cmp_node_defocus_declare;
ntype.draw_buttons = node_composit_buts_defocus;
node_type_init(&ntype, node_composit_init_defocus);

View File

@ -76,7 +76,7 @@ void register_node_type_cmp_denoise()
{
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_DENOISE, "Denoise", NODE_CLASS_OP_FILTER, 0);
cmp_node_type_base(&ntype, CMP_NODE_DENOISE, "Denoise", NODE_CLASS_OP_FILTER);
ntype.declare = blender::nodes::cmp_node_denoise_declare;
ntype.draw_buttons = node_composit_buts_denoise;
node_type_init(&ntype, node_composit_init_denonise);

View File

@ -58,9 +58,10 @@ void register_node_type_cmp_despeckle()
{
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_DESPECKLE, "Despeckle", NODE_CLASS_OP_FILTER, NODE_PREVIEW);
cmp_node_type_base(&ntype, CMP_NODE_DESPECKLE, "Despeckle", NODE_CLASS_OP_FILTER);
ntype.declare = blender::nodes::cmp_node_despeckle_declare;
ntype.draw_buttons = node_composit_buts_despeckle;
ntype.flag |= NODE_PREVIEW;
node_type_init(&ntype, node_composit_init_despeckle);
nodeRegisterType(&ntype);

View File

@ -62,10 +62,10 @@ void register_node_type_cmp_diff_matte()
{
static bNodeType ntype;
cmp_node_type_base(
&ntype, CMP_NODE_DIFF_MATTE, "Difference Key", NODE_CLASS_MATTE, NODE_PREVIEW);
cmp_node_type_base(&ntype, CMP_NODE_DIFF_MATTE, "Difference Key", NODE_CLASS_MATTE);
ntype.declare = blender::nodes::cmp_node_diff_matte_declare;
ntype.draw_buttons = node_composit_buts_diff_matte;
ntype.flag |= NODE_PREVIEW;
node_type_init(&ntype, node_composit_init_diff_matte);
node_type_storage(&ntype, "NodeChroma", node_free_standard_storage, node_copy_standard_storage);

View File

@ -65,7 +65,7 @@ void register_node_type_cmp_dilateerode()
{
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_DILATEERODE, "Dilate/Erode", NODE_CLASS_OP_FILTER, 0);
cmp_node_type_base(&ntype, CMP_NODE_DILATEERODE, "Dilate/Erode", NODE_CLASS_OP_FILTER);
ntype.draw_buttons = node_composit_buts_dilateerode;
ntype.declare = blender::nodes::cmp_node_dilate_declare;
node_type_init(&ntype, node_composit_init_dilateerode);

View File

@ -73,7 +73,7 @@ void register_node_type_cmp_dblur()
{
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_DBLUR, "Directional Blur", NODE_CLASS_OP_FILTER, 0);
cmp_node_type_base(&ntype, CMP_NODE_DBLUR, "Directional Blur", NODE_CLASS_OP_FILTER);
ntype.declare = blender::nodes::cmp_node_directional_blur_declare;
ntype.draw_buttons = node_composit_buts_dblur;
node_type_init(&ntype, node_composit_init_dblur);

View File

@ -46,7 +46,7 @@ void register_node_type_cmp_displace()
{
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_DISPLACE, "Displace", NODE_CLASS_DISTORT, 0);
cmp_node_type_base(&ntype, CMP_NODE_DISPLACE, "Displace", NODE_CLASS_DISTORT);
ntype.declare = blender::nodes::cmp_node_displace_declare;
nodeRegisterType(&ntype);

View File

@ -70,9 +70,10 @@ void register_node_type_cmp_distance_matte()
{
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_DIST_MATTE, "Distance Key", NODE_CLASS_MATTE, NODE_PREVIEW);
cmp_node_type_base(&ntype, CMP_NODE_DIST_MATTE, "Distance Key", NODE_CLASS_MATTE);
ntype.declare = blender::nodes::cmp_node_distance_matte_declare;
ntype.draw_buttons = node_composit_buts_distance_matte;
ntype.flag |= NODE_PREVIEW;
node_type_init(&ntype, node_composit_init_distance_matte);
node_type_storage(&ntype, "NodeChroma", node_free_standard_storage, node_copy_standard_storage);

View File

@ -57,7 +57,7 @@ void register_node_type_cmp_doubleedgemask()
{
static bNodeType ntype; /* Allocate a node type data structure. */
cmp_node_type_base(&ntype, CMP_NODE_DOUBLEEDGEMASK, "Double Edge Mask", NODE_CLASS_MATTE, 0);
cmp_node_type_base(&ntype, CMP_NODE_DOUBLEEDGEMASK, "Double Edge Mask", NODE_CLASS_MATTE);
ntype.declare = blender::nodes::cmp_node_double_edge_mask_declare;
ntype.draw_buttons = node_composit_buts_double_edge_mask;

View File

@ -68,7 +68,7 @@ void register_node_type_cmp_ellipsemask()
{
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_MASK_ELLIPSE, "Ellipse Mask", NODE_CLASS_MATTE, 0);
cmp_node_type_base(&ntype, CMP_NODE_MASK_ELLIPSE, "Ellipse Mask", NODE_CLASS_MATTE);
ntype.declare = blender::nodes::cmp_node_ellipsemask_declare;
ntype.draw_buttons = node_composit_buts_ellipsemask;
node_type_size(&ntype, 260, 110, 320);

View File

@ -40,7 +40,7 @@ void register_node_type_cmp_exposure()
{
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_EXPOSURE, "Exposure", NODE_CLASS_OP_COLOR, 0);
cmp_node_type_base(&ntype, CMP_NODE_EXPOSURE, "Exposure", NODE_CLASS_OP_COLOR);
ntype.declare = blender::nodes::cmp_node_exposure_declare;
nodeRegisterType(&ntype);

View File

@ -48,10 +48,11 @@ void register_node_type_cmp_filter()
{
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_FILTER, "Filter", NODE_CLASS_OP_FILTER, NODE_PREVIEW);
cmp_node_type_base(&ntype, CMP_NODE_FILTER, "Filter", NODE_CLASS_OP_FILTER);
ntype.declare = blender::nodes::cmp_node_filter_declare;
ntype.draw_buttons = node_composit_buts_filter;
ntype.labelfunc = node_filter_label;
ntype.flag |= NODE_PREVIEW;
nodeRegisterType(&ntype);
}

View File

@ -47,7 +47,7 @@ void register_node_type_cmp_flip()
{
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_FLIP, "Flip", NODE_CLASS_DISTORT, 0);
cmp_node_type_base(&ntype, CMP_NODE_FLIP, "Flip", NODE_CLASS_DISTORT);
ntype.declare = blender::nodes::cmp_node_flip_declare;
ntype.draw_buttons = node_composit_buts_flip;

View File

@ -44,7 +44,7 @@ void register_node_type_cmp_gamma()
{
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_GAMMA, "Gamma", NODE_CLASS_OP_COLOR, 0);
cmp_node_type_base(&ntype, CMP_NODE_GAMMA, "Gamma", NODE_CLASS_OP_COLOR);
ntype.declare = blender::nodes::cmp_node_gamma_declare;
nodeRegisterType(&ntype);

View File

@ -97,7 +97,7 @@ void register_node_type_cmp_glare()
{
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_GLARE, "Glare", NODE_CLASS_OP_FILTER, 0);
cmp_node_type_base(&ntype, CMP_NODE_GLARE, "Glare", NODE_CLASS_OP_FILTER);
ntype.declare = blender::nodes::cmp_node_glare_declare;
ntype.draw_buttons = node_composit_buts_glare;
node_type_init(&ntype, node_composit_init_glare);

Some files were not shown because too many files have changed in this diff Show More