WIP: use generic copy-on-write system to avoid unnecessary data copies #104470
|
@ -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*"
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -5901,7 +5901,7 @@ VectorMapRangeNode::VectorMapRangeNode() : ShaderNode(get_node_type())
|
|||
{
|
||||
}
|
||||
|
||||
void VectorMapRangeNode::expand(ShaderGraph *graph)
|
||||
void VectorMapRangeNode::expand(ShaderGraph * /*graph*/)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
Binary file not shown.
|
@ -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`)!
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)")
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
/** \} */
|
||||
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -26,6 +26,7 @@ set(INC
|
|||
../blentranslation
|
||||
../depsgraph
|
||||
../draw
|
||||
../editors/include
|
||||
../imbuf
|
||||
../makesdna
|
||||
../makesrna
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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++;
|
||||
|
|
|
@ -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) **** */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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", "");
|
||||
}
|
|
@ -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);
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -684,7 +684,7 @@ const CPPType &FieldConstant::type() const
|
|||
return type_;
|
||||
}
|
||||
|
||||
const GPointer FieldConstant::value() const
|
||||
GPointer FieldConstant::value() const
|
||||
{
|
||||
return {type_, value_};
|
||||
}
|
||||
|
|
|
@ -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_);
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
# ***** END GPL LICENSE BLOCK *****
|
||||
|
||||
add_subdirectory(common)
|
||||
add_subdirectory(wavefront_obj)
|
||||
|
||||
if(WITH_ALEMBIC)
|
||||
add_subdirectory(alembic)
|
||||
|
|
|
@ -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()
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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 smooth–shaded, 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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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 ¶ms,
|
||||
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 ¶ms)
|
||||
{
|
||||
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
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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", "");
|
||||
}
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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", "")
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue