Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,118 @@
# SPDX-FileCopyrightText: 2023 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
set(INC
../include
../../io/alembic
../../io/common
../../io/fbx
../../io/grease_pencil
../../io/ply
../../io/stl
../../io/usd
../../io/wavefront_obj
../../makesrna
${CMAKE_BINARY_DIR}/source/blender/makesrna
)
set(INC_SYS
)
set(SRC
io_alembic.cc
io_cache.cc
io_drop_import_file.cc
io_fbx_ops.cc
io_grease_pencil.cc
io_obj.cc
io_ops.cc
io_ply_ops.cc
io_stl_ops.cc
io_usd.cc
io_utils.cc
io_alembic.hh
io_cache.hh
io_drop_import_file.hh
io_fbx_ops.hh
io_grease_pencil.hh
io_obj.hh
io_ops.hh
io_ply_ops.hh
io_stl_ops.hh
io_usd.hh
io_utils.hh
)
set(LIB
PRIVATE bf::blenkernel
PRIVATE bf::blenlib
PRIVATE bf::blentranslation
PRIVATE bf::bmesh
PRIVATE bf::depsgraph
PRIVATE bf::dna
PRIVATE bf::intern::guardedalloc
PRIVATE bf::intern::clog
PRIVATE bf::windowmanager
)
if(WITH_IO_WAVEFRONT_OBJ)
list(APPEND LIB
bf_io_wavefront_obj
)
add_definitions(-DWITH_IO_WAVEFRONT_OBJ)
endif()
if(WITH_IO_PLY)
list(APPEND LIB
bf_io_ply
)
add_definitions(-DWITH_IO_PLY)
endif()
if(WITH_IO_STL)
list(APPEND LIB
bf_io_stl
)
add_definitions(-DWITH_IO_STL)
endif()
if(WITH_IO_FBX)
list(APPEND LIB
bf_io_fbx
)
add_definitions(-DWITH_IO_FBX)
endif()
if(WITH_IO_GREASE_PENCIL)
list(APPEND LIB
bf_io_grease_pencil
)
add_definitions(-DWITH_IO_GREASE_PENCIL)
endif()
if(WITH_ALEMBIC)
list(APPEND LIB
bf_io_alembic
)
add_definitions(-DWITH_ALEMBIC)
endif()
if(WITH_USD)
list(APPEND LIB
bf_io_usd
PRIVATE bf::dependencies::optional::usd
)
add_definitions(-DWITH_USD)
endif()
if(WITH_PUGIXML)
add_definitions(-DWITH_PUGIXML)
endif()
if(WITH_HARU)
add_definitions(-DWITH_HARU)
endif()
blender_add_lib(bf_editor_io "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")

View File

@@ -0,0 +1,738 @@
/* SPDX-FileCopyrightText: 2016 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup editor/io
*/
#ifdef WITH_ALEMBIC
/* needed for directory lookup */
# ifndef WIN32
# include <dirent.h>
# else
# include "BLI_winstuff.h"
# endif
# include <cerrno>
# include <cstring>
# include "DNA_modifier_types.h"
# include "DNA_object_types.h"
# include "DNA_scene_types.h"
# include "DNA_space_enums.h"
# include "BKE_context.hh"
# include "BKE_file_handler.hh"
# include "BKE_main.hh"
# include "BKE_report.hh"
# include "BLI_path_utils.hh"
# include "BLI_string_utf8.h"
# include "BLI_utildefines.h"
# include "BLI_vector.hh"
# include "BLT_translation.hh"
# include "RNA_access.hh"
# include "RNA_define.hh"
# include "RNA_enum_types.hh"
# include "ED_fileselect.hh"
# include "ED_object.hh"
# include "UI_interface.hh"
# include "UI_resources.hh"
# include "WM_api.hh"
# include "WM_types.hh"
# include "DEG_depsgraph.hh"
# include "io_alembic.hh"
# include "io_utils.hh"
# include "ABC_alembic.h"
# include "UI_interface_layout.hh"
# include "CLG_log.h"
namespace blender {
static CLG_LogRef LOG = {"io.alembic"};
static const EnumPropertyItem rna_enum_abc_export_evaluation_mode_items[] = {
{DAG_EVAL_RENDER,
"RENDER",
0,
"Render",
"Use Render settings for object visibility, modifier settings, etc"},
{DAG_EVAL_VIEWPORT,
"VIEWPORT",
0,
"Viewport",
"Use Viewport settings for object visibility, modifier settings, etc"},
{0, nullptr, 0, nullptr, nullptr},
};
static wmOperatorStatus wm_alembic_export_invoke(bContext *C,
wmOperator *op,
const wmEvent * /*event*/)
{
if (!RNA_struct_property_is_set(op->ptr, "as_background_job")) {
RNA_boolean_set(op->ptr, "as_background_job", true);
}
RNA_boolean_set(op->ptr, "init_scene_frame_range", true);
ED_fileselect_ensure_default_filepath(C, op, ".abc");
WM_event_add_fileselect(C, op);
return OPERATOR_RUNNING_MODAL;
}
static wmOperatorStatus wm_alembic_export_exec(bContext *C, wmOperator *op)
{
if (!RNA_struct_property_is_set_ex(op->ptr, "filepath", false)) {
BKE_report(op->reports, RPT_ERROR, "No filepath given");
return OPERATOR_CANCELLED;
}
char filepath[FILE_MAX];
RNA_string_get(op->ptr, "filepath", filepath);
AlembicExportParams params{};
params.frame_start = RNA_int_get(op->ptr, "start");
params.frame_end = RNA_int_get(op->ptr, "end");
params.frame_samples_xform = RNA_int_get(op->ptr, "xsamples");
params.frame_samples_shape = RNA_int_get(op->ptr, "gsamples");
params.shutter_open = RNA_float_get(op->ptr, "sh_open");
params.shutter_close = RNA_float_get(op->ptr, "sh_close");
params.selected_only = RNA_boolean_get(op->ptr, "selected");
params.uvs = RNA_boolean_get(op->ptr, "uvs");
params.normals = RNA_boolean_get(op->ptr, "normals");
params.vcolors = RNA_boolean_get(op->ptr, "vcolors");
params.orcos = RNA_boolean_get(op->ptr, "orcos");
params.apply_subdiv = RNA_boolean_get(op->ptr, "apply_subdiv");
params.curves_as_mesh = RNA_boolean_get(op->ptr, "curves_as_mesh");
params.flatten_hierarchy = RNA_boolean_get(op->ptr, "flatten");
params.face_sets = RNA_boolean_get(op->ptr, "face_sets");
params.use_subdiv_schema = RNA_boolean_get(op->ptr, "subdiv_schema");
params.export_hair = RNA_boolean_get(op->ptr, "export_hair");
params.export_particles = RNA_boolean_get(op->ptr, "export_particles");
params.export_custom_properties = RNA_boolean_get(op->ptr, "export_custom_properties");
params.use_instancing = RNA_boolean_get(op->ptr, "use_instancing");
params.packuv = RNA_boolean_get(op->ptr, "packuv");
params.triangulate = RNA_boolean_get(op->ptr, "triangulate");
params.quad_method = RNA_enum_get(op->ptr, "quad_method");
params.ngon_method = RNA_enum_get(op->ptr, "ngon_method");
params.evaluation_mode = eEvaluationMode(RNA_enum_get(op->ptr, "evaluation_mode"));
params.global_scale = RNA_float_get(op->ptr, "global_scale");
RNA_string_get(op->ptr, "collection", params.collection);
/* Take some defaults from the scene, if not specified explicitly. */
Scene *scene = CTX_data_scene(C);
if (params.frame_start == INT_MIN) {
params.frame_start = scene->r.sfra;
}
if (params.frame_end == INT_MIN) {
params.frame_end = scene->r.efra;
}
const bool as_background_job = RNA_boolean_get(op->ptr, "as_background_job");
bool ok = ABC_export(scene, C, filepath, &params, as_background_job);
return as_background_job || ok ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
}
static void ui_alembic_export_settings(const bContext *C, ui::Layout &layout, PointerRNA *ptr)
{
layout.use_property_split_set(true);
layout.use_property_decorate_set(false);
if (ui::Layout *panel = layout.panel(C, "ABC_export_general", false, IFACE_("General"))) {
ui::Layout *col = &panel->column(false);
col->prop(ptr, "global_scale", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col = &panel->column(false);
if (CTX_wm_space_file(C)) {
ui::Layout &sub = col->column(true, IFACE_("Include"));
sub.prop(ptr, "selected", UI_ITEM_NONE, IFACE_("Selection Only"), ICON_NONE);
}
}
/* Scene Options */
if (ui::Layout *panel = layout.panel(C, "ABC_export_scene", false, IFACE_("Scene"))) {
ui::Layout *col = &panel->column(false);
ui::Layout *sub = &col->column(true);
sub->prop(ptr, "start", UI_ITEM_NONE, IFACE_("Frame Start"), ICON_NONE);
sub->prop(ptr, "end", UI_ITEM_NONE, IFACE_("End"), ICON_NONE);
sub = &col->column(true);
sub->prop(ptr, "xsamples", UI_ITEM_NONE, IFACE_("Samples Transform"), ICON_NONE);
sub->prop(ptr, "gsamples", UI_ITEM_NONE, IFACE_("Geometry"), ICON_NONE);
sub = &col->column(true);
sub->prop(ptr, "sh_open", ui::ITEM_R_SLIDER, std::nullopt, ICON_NONE);
sub->prop(ptr,
"sh_close",
ui::ITEM_R_SLIDER,
CTX_IFACE_(BLT_I18NCONTEXT_ID_CAMERA, "Close"),
ICON_NONE);
col->separator();
col->prop(ptr, "use_instancing", UI_ITEM_NONE, IFACE_("Use Instancing"), ICON_NONE);
col->prop(
ptr, "export_custom_properties", UI_ITEM_NONE, IFACE_("Custom Properties"), ICON_NONE);
col->prop(ptr, "flatten", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col = &panel->column(true);
col->prop(ptr, "evaluation_mode", UI_ITEM_NONE, std::nullopt, ICON_NONE);
}
/* Object Data */
if (ui::Layout *panel = layout.panel(C, "ABC_export_geometry", false, IFACE_("Geometry"))) {
ui::Layout *col = &panel->column(true);
col->prop(ptr, "uvs", UI_ITEM_NONE, std::nullopt, ICON_NONE);
ui::Layout &row = col->row(false);
row.active_set(RNA_boolean_get(ptr, "uvs"));
row.prop(ptr, "packuv", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col->prop(ptr, "normals", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col->prop(ptr, "vcolors", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col->prop(ptr, "orcos", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col->prop(ptr, "face_sets", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col->prop(ptr, "curves_as_mesh", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col->separator();
ui::Layout *sub = &col->column(true, IFACE_("Subdivision"));
sub->prop(ptr, "apply_subdiv", UI_ITEM_NONE, IFACE_("Apply"), ICON_NONE);
sub->prop(ptr, "subdiv_schema", UI_ITEM_NONE, IFACE_("Use Schema"), ICON_NONE);
col = &panel->column(false);
col->prop(ptr, "triangulate", UI_ITEM_NONE, std::nullopt, ICON_NONE);
sub = &col->column(false);
sub->active_set(RNA_boolean_get(ptr, "triangulate"));
sub->prop(ptr, "quad_method", UI_ITEM_NONE, IFACE_("Method Quads"), ICON_NONE);
sub->prop(ptr, "ngon_method", UI_ITEM_NONE, IFACE_("Polygons"), ICON_NONE);
}
/* Particle Data */
if (ui::Layout *panel = layout.panel(
C, "ABC_export_particles", false, IFACE_("Particle Systems")))
{
ui::Layout &col = panel->column(true);
col.prop(ptr, "export_hair", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col.prop(ptr, "export_particles", UI_ITEM_NONE, std::nullopt, ICON_NONE);
}
}
static void wm_alembic_export_draw(bContext *C, wmOperator *op)
{
/* Conveniently set start and end frame to match the scene's frame range. */
Scene *scene = CTX_data_scene(C);
if (scene != nullptr && RNA_boolean_get(op->ptr, "init_scene_frame_range")) {
RNA_int_set(op->ptr, "start", scene->r.sfra);
RNA_int_set(op->ptr, "end", scene->r.efra);
RNA_boolean_set(op->ptr, "init_scene_frame_range", false);
}
ui_alembic_export_settings(C, *op->layout, op->ptr);
}
static bool wm_alembic_export_check(bContext * /*C*/, wmOperator *op)
{
char filepath[FILE_MAX];
RNA_string_get(op->ptr, "filepath", filepath);
if (!BLI_path_extension_check(filepath, ".abc")) {
BLI_path_extension_ensure(filepath, FILE_MAX, ".abc");
RNA_string_set(op->ptr, "filepath", filepath);
return true;
}
return false;
}
void WM_OT_alembic_export(wmOperatorType *ot)
{
ot->name = "Export Alembic";
ot->description = "Export current scene in an Alembic archive";
ot->idname = "WM_OT_alembic_export";
ot->invoke = wm_alembic_export_invoke;
ot->exec = wm_alembic_export_exec;
ot->poll = WM_operator_winactive;
ot->ui = wm_alembic_export_draw;
ot->check = wm_alembic_export_check;
ot->flag = OPTYPE_PRESET;
WM_operator_properties_filesel(ot,
FILE_TYPE_FOLDER | FILE_TYPE_ALEMBIC,
FILE_BLENDER,
FILE_SAVE,
WM_FILESEL_FILEPATH | WM_FILESEL_SHOW_PROPS,
FILE_DEFAULTDISPLAY,
FILE_SORT_DEFAULT);
PropertyRNA *prop = RNA_def_string(ot->srna, "filter_glob", "*.abc", 0, "", "");
RNA_def_property_flag(prop, PROP_HIDDEN);
RNA_def_int(ot->srna,
"start",
INT_MIN,
INT_MIN,
INT_MAX,
"Start Frame",
"Start frame of the export, use the default value to "
"take the start frame of the current scene",
INT_MIN,
INT_MAX);
RNA_def_int(ot->srna,
"end",
INT_MIN,
INT_MIN,
INT_MAX,
"End Frame",
"End frame of the export, use the default value to "
"take the end frame of the current scene",
INT_MIN,
INT_MAX);
RNA_def_int(ot->srna,
"xsamples",
1,
1,
128,
"Transform Samples",
"Number of times per frame transformations are sampled",
1,
128);
RNA_def_int(ot->srna,
"gsamples",
1,
1,
128,
"Geometry Samples",
"Number of times per frame object data are sampled",
1,
128);
RNA_def_float(ot->srna,
"sh_open",
0.0f,
-1.0f,
1.0f,
"Shutter Open",
"Time at which the shutter is open",
-1.0f,
1.0f);
RNA_def_float(ot->srna,
"sh_close",
1.0f,
-1.0f,
1.0f,
"Shutter Close",
"Time at which the shutter is closed",
-1.0f,
1.0f);
RNA_def_boolean(
ot->srna, "selected", false, "Selected Objects Only", "Export only selected objects");
RNA_def_boolean(ot->srna,
"flatten",
false,
"Flatten Hierarchy",
"Do not preserve objects' parent/children relationship");
prop = RNA_def_string(ot->srna, "collection", nullptr, MAX_ID_NAME - 2, "Collection", nullptr);
RNA_def_property_flag(prop, PROP_HIDDEN);
RNA_def_boolean(ot->srna, "uvs", true, "UV Coordinates", "Export UV coordinates");
RNA_def_boolean(ot->srna, "packuv", true, "Merge UVs", "");
RNA_def_boolean(ot->srna, "normals", true, "Normals", "Export normals");
RNA_def_boolean(ot->srna, "vcolors", false, "Color Attributes", "Export color attributes");
RNA_def_boolean(ot->srna,
"orcos",
true,
"Generated Coordinates",
"Export undeformed mesh vertex coordinates");
RNA_def_boolean(
ot->srna, "face_sets", false, "Face Sets", "Export per face shading group assignments");
RNA_def_boolean(ot->srna,
"subdiv_schema",
false,
"Use Subdivision Schema",
"Export meshes using Alembic's subdivision schema");
RNA_def_boolean(ot->srna,
"apply_subdiv",
false,
"Apply Subdivision Surface",
"Export subdivision surfaces as meshes");
RNA_def_boolean(ot->srna,
"curves_as_mesh",
false,
"Curves as Mesh",
"Export curves and NURBS surfaces as meshes");
RNA_def_boolean(ot->srna,
"use_instancing",
true,
"Use Instancing",
"Export data of duplicated objects as Alembic instances; speeds up the export "
"and can be disabled for compatibility with other software");
RNA_def_float(
ot->srna,
"global_scale",
1.0f,
0.0001f,
1000.0f,
"Scale",
"Value by which to enlarge or shrink the objects with respect to the world's origin",
0.0001f,
1000.0f);
RNA_def_boolean(ot->srna,
"triangulate",
false,
"Triangulate",
"Export polygons (quads and n-gons) as triangles");
RNA_def_enum(ot->srna,
"quad_method",
rna_enum_modifier_triangulate_quad_method_items,
MOD_TRIANGULATE_QUAD_SHORTEDGE,
"Quad Method",
"Method for splitting the quads into triangles");
RNA_def_enum(ot->srna,
"ngon_method",
rna_enum_modifier_triangulate_ngon_method_items,
MOD_TRIANGULATE_NGON_BEAUTY,
"N-gon Method",
"Method for splitting the n-gons into triangles");
RNA_def_boolean(ot->srna,
"export_hair",
true,
"Export Hair",
"Exports hair particle systems as animated curves");
RNA_def_boolean(
ot->srna, "export_particles", true, "Export Particles", "Exports non-hair particle systems");
RNA_def_boolean(ot->srna,
"export_custom_properties",
true,
"Export Custom Properties",
"Export custom properties to Alembic .userProperties");
RNA_def_boolean(
ot->srna,
"as_background_job",
false,
"Run as Background Job",
"Enable this to run the import in the background, disable to block Blender while importing. "
"This option is deprecated; EXECUTE this operator to run in the foreground, and INVOKE it "
"to run as a background job");
RNA_def_enum(ot->srna,
"evaluation_mode",
rna_enum_abc_export_evaluation_mode_items,
DAG_EVAL_RENDER,
"Settings",
"Determines visibility of objects, modifier settings, and other areas where there "
"are different settings for viewport and rendering");
/* This dummy prop is used to check whether we need to init the start and
* end frame values to that of the scene's, otherwise they are reset at
* every change, draw update. */
RNA_def_boolean(ot->srna, "init_scene_frame_range", true, "", "");
}
/* ************************************************************************** */
/* TODO(kevin): check on de-duplicating all this with code in `image_ops.cc` */
struct CacheFrame {
CacheFrame *next, *prev;
int framenr;
};
static int get_sequence_len(const char *filepath, int *ofs)
{
int frame;
int numdigit;
if (!BLI_path_frame_get(filepath, &frame, &numdigit)) {
return 1;
}
char dirpath[FILE_MAX];
BLI_path_split_dir_part(filepath, dirpath, FILE_MAX);
if (dirpath[0] == '\0') {
/* The `filepath` had no directory component, so just use the blend files directory. */
BLI_path_split_dir_part(BKE_main_blendfile_path_from_global(), dirpath, sizeof(dirpath));
}
else {
BLI_path_abs(dirpath, BKE_main_blendfile_path_from_global());
}
DIR *dir = opendir(dirpath);
if (dir == nullptr) {
CLOG_ERROR(&LOG,
"Error opening directory '%s': %s",
dirpath,
errno ? strerror(errno) : "unknown error");
return -1;
}
const char *ext = ".abc";
const char *basename = BLI_path_basename(filepath);
const int len = strlen(basename) - (numdigit + strlen(ext));
Vector<CacheFrame> frames;
dirent *fname;
while ((fname = readdir(dir)) != nullptr) {
/* do we have the right extension? */
if (!strstr(fname->d_name, ext)) {
continue;
}
if (!STREQLEN(basename, fname->d_name, len)) {
continue;
}
CacheFrame cache_frame{};
BLI_path_frame_get(fname->d_name, &cache_frame.framenr, &numdigit);
frames.append(cache_frame);
}
closedir(dir);
std::ranges::sort(
frames, [](const CacheFrame &a, const CacheFrame &b) { return a.framenr < b.framenr; });
if (frames.is_empty()) {
return -1;
}
int frame_curr = frames.first().framenr;
(*ofs) = frame_curr;
for (CacheFrame &cache_frame : frames) {
if (cache_frame.framenr != frame_curr) {
break;
}
frame_curr++;
}
return frame_curr - (*ofs);
}
/* ************************************************************************** */
static void ui_alembic_import_settings(const bContext *C, ui::Layout *layout, PointerRNA *ptr)
{
layout->use_property_split_set(true);
layout->use_property_decorate_set(false);
if (ui::Layout *panel = layout->panel(C, "ABC_import_general", false, IFACE_("General"))) {
ui::Layout &col = panel->column(false);
col.prop(ptr, "scale", UI_ITEM_NONE, std::nullopt, ICON_NONE);
}
if (ui::Layout *panel = layout->panel(C, "ABC_import_options", false, IFACE_("Options"))) {
ui::Layout &col = panel->column(false);
col.prop(ptr, "relative_path", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col.prop(ptr, "set_frame_range", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col.prop(ptr, "is_sequence", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col.prop(ptr, "validate_meshes", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col.prop(ptr, "always_add_cache_reader", UI_ITEM_NONE, std::nullopt, ICON_NONE);
}
}
static void wm_alembic_import_draw(bContext *C, wmOperator *op)
{
ui_alembic_import_settings(C, op->layout, op->ptr);
}
/* op->invoke, opens fileselect if path property not set, otherwise executes */
static wmOperatorStatus wm_alembic_import_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
if (!RNA_struct_property_is_set(op->ptr, "as_background_job")) {
RNA_boolean_set(op->ptr, "as_background_job", true);
}
return ed::io::filesel_drop_import_invoke(C, op, event);
}
static wmOperatorStatus wm_alembic_import_exec(bContext *C, wmOperator *op)
{
Vector<std::string> paths = ed::io::paths_from_operator_properties(op->ptr);
if (paths.is_empty()) {
BKE_report(op->reports, RPT_ERROR, "No filepath given");
return OPERATOR_CANCELLED;
}
const float scale = RNA_float_get(op->ptr, "scale");
const bool is_sequence = RNA_boolean_get(op->ptr, "is_sequence");
const bool set_frame_range = RNA_boolean_get(op->ptr, "set_frame_range");
const bool validate_meshes = RNA_boolean_get(op->ptr, "validate_meshes");
const bool always_add_cache_reader = RNA_boolean_get(op->ptr, "always_add_cache_reader");
const bool as_background_job = RNA_boolean_get(op->ptr, "as_background_job");
int sequence_min_frame = std::numeric_limits<int>::max();
int sequence_max_frame = std::numeric_limits<int>::min();
if (is_sequence) {
for (const std::string &path : paths) {
int offset = 0;
int sequence_len = get_sequence_len(path.c_str(), &offset);
if (sequence_len < 0) {
BKE_report(op->reports, RPT_ERROR, "Unable to determine ABC sequence length");
return OPERATOR_CANCELLED;
}
sequence_min_frame = std::min(sequence_min_frame, offset);
sequence_max_frame = std::max(sequence_max_frame, offset + (sequence_len - 1));
}
}
/* Switch out of edit mode to avoid being stuck in it (#54326). */
Object *obedit = CTX_data_edit_object(C);
if (obedit) {
ed::object::mode_set(C, OB_MODE_OBJECT);
}
AlembicImportParams params{};
params.paths = std::move(paths);
params.global_scale = scale;
params.sequence_min_frame = sequence_min_frame;
params.sequence_max_frame = sequence_max_frame;
params.is_sequence = is_sequence;
params.set_frame_range = set_frame_range;
params.validate_meshes = validate_meshes;
params.always_add_cache_reader = always_add_cache_reader;
bool ok = ABC_import(C, &params, as_background_job);
return as_background_job || ok ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
}
void WM_OT_alembic_import(wmOperatorType *ot)
{
ot->name = "Import Alembic";
ot->description = "Load an Alembic archive";
ot->idname = "WM_OT_alembic_import";
ot->flag = OPTYPE_UNDO | OPTYPE_PRESET;
ot->invoke = wm_alembic_import_invoke;
ot->exec = wm_alembic_import_exec;
ot->poll = WM_operator_winactive;
ot->ui = wm_alembic_import_draw;
WM_operator_properties_filesel(ot,
FILE_TYPE_FOLDER | FILE_TYPE_ALEMBIC,
FILE_BLENDER,
FILE_OPENFILE,
WM_FILESEL_FILEPATH | WM_FILESEL_RELPATH | WM_FILESEL_SHOW_PROPS |
WM_FILESEL_DIRECTORY | WM_FILESEL_FILES,
FILE_DEFAULTDISPLAY,
FILE_SORT_DEFAULT);
PropertyRNA *prop = RNA_def_string(ot->srna, "filter_glob", "*.abc", 0, "", "");
RNA_def_property_flag(prop, PROP_HIDDEN);
RNA_def_float(
ot->srna,
"scale",
1.0f,
0.0001f,
1000.0f,
"Scale",
"Value by which to enlarge or shrink the objects with respect to the world's origin",
0.0001f,
1000.0f);
RNA_def_boolean(
ot->srna,
"set_frame_range",
true,
"Set Frame Range",
"If checked, update scene's start and end frame to match those of the Alembic archive");
RNA_def_boolean(
ot->srna,
"validate_meshes",
false,
"Validate Meshes",
"Ensure the data is valid "
"(when disabled, data may be imported which causes crashes displaying or editing)");
RNA_def_boolean(ot->srna,
"always_add_cache_reader",
false,
"Always Add Cache Reader",
"Add cache modifiers and constraints to imported objects even if they are not "
"animated so that they can be updated when reloading the Alembic archive");
RNA_def_boolean(ot->srna,
"is_sequence",
false,
"Is Sequence",
"Set to true if the cache is split into separate files");
RNA_def_boolean(
ot->srna,
"as_background_job",
false,
"Run as Background Job",
"Enable this to run the export in the background, disable to block Blender while exporting. "
"This option is deprecated; EXECUTE this operator to run in the foreground, and INVOKE it "
"to run as a background job");
}
namespace ed::io {
void alembic_file_handler_add()
{
auto fh = std::make_unique<bke::FileHandlerType>();
STRNCPY_UTF8(fh->idname, "IO_FH_alembic");
STRNCPY_UTF8(fh->import_operator, "WM_OT_alembic_import");
STRNCPY_UTF8(fh->export_operator, "WM_OT_alembic_export");
STRNCPY_UTF8(fh->label, "Alembic");
STRNCPY_UTF8(fh->file_extensions_str, ".abc");
fh->poll_drop = poll_file_object_drop;
bke::file_handler_add(std::move(fh));
}
} // namespace ed::io
} // namespace blender
#endif

View File

@@ -0,0 +1,22 @@
/* SPDX-FileCopyrightText: 2016 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup editor/io
*/
namespace blender {
struct wmOperatorType;
void WM_OT_alembic_export(wmOperatorType *ot);
void WM_OT_alembic_import(wmOperatorType *ot);
namespace ed::io {
void alembic_file_handler_add();
}
} // namespace blender

View File

@@ -0,0 +1,323 @@
/* SPDX-FileCopyrightText: 2016 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup editor/io
*/
#include "MEM_guardedalloc.h"
#include "DNA_cachefile_types.h"
#include "DNA_space_types.h"
#include "BLI_listbase.h"
#include "BLI_path_utils.hh"
#include "BLI_string.h"
#include "BKE_cachefile.hh"
#include "BKE_context.hh"
#include "BKE_lib_id.hh"
#include "BKE_main.hh"
#include "BKE_report.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "DEG_depsgraph.hh"
#include "UI_interface.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "io_cache.hh"
namespace blender {
static void reload_cachefile(bContext *C, CacheFile *cache_file)
{
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
BKE_cachefile_reload(depsgraph, cache_file);
}
static void cachefile_init(bContext *C, wmOperator *op)
{
PropertyPointerRNA *pprop;
op->customdata = pprop = MEM_new<PropertyPointerRNA>("OpenPropertyPointerRNA");
ui::context_active_but_prop_get_templateID(C, &pprop->ptr, &pprop->prop);
}
static wmOperatorStatus cachefile_open_invoke(bContext *C,
wmOperator *op,
const wmEvent * /*event*/)
{
if (!RNA_struct_property_is_set(op->ptr, "filepath")) {
char filepath[FILE_MAX];
Main *bmain = CTX_data_main(C);
/* Default to the same directory as the blend file. */
BLI_path_split_dir_part(BKE_main_blendfile_path(bmain), filepath, sizeof(filepath));
RNA_string_set(op->ptr, "filepath", filepath);
}
cachefile_init(C, op);
WM_event_add_fileselect(C, op);
return OPERATOR_RUNNING_MODAL;
}
static void open_cancel(bContext * /*C*/, wmOperator *op)
{
if (op->customdata) {
PropertyPointerRNA *prop_ptr = static_cast<PropertyPointerRNA *>(op->customdata);
op->customdata = nullptr;
MEM_delete(prop_ptr);
}
}
static wmOperatorStatus cachefile_open_exec(bContext *C, wmOperator *op)
{
if (!RNA_struct_property_is_set(op->ptr, "filepath")) {
BKE_report(op->reports, RPT_ERROR, "No filepath given");
return OPERATOR_CANCELLED;
}
char filepath[FILE_MAX];
RNA_string_get(op->ptr, "filepath", filepath);
Main *bmain = CTX_data_main(C);
CacheFile *cache_file = static_cast<CacheFile *>(
BKE_libblock_alloc(bmain, ID_CF, BLI_path_basename(filepath), 0));
STRNCPY(cache_file->filepath, filepath);
DEG_id_tag_update(&cache_file->id, ID_RECALC_SYNC_TO_EVAL);
/* Will be set when running invoke, not exec directly. */
if (op->customdata != nullptr) {
/* hook into UI */
PropertyPointerRNA *pprop = static_cast<PropertyPointerRNA *>(op->customdata);
if (pprop->prop != nullptr) {
/* When creating new ID blocks, use is already 1, but RNA
* pointer see also increases user, so this compensates it. */
id_us_min(&cache_file->id);
PointerRNA idptr = RNA_id_pointer_create(&cache_file->id);
RNA_property_pointer_set(&pprop->ptr, pprop->prop, idptr, nullptr);
RNA_property_update(C, &pprop->ptr, pprop->prop);
}
op->customdata = nullptr;
MEM_delete(pprop);
}
return OPERATOR_FINISHED;
}
void CACHEFILE_OT_open(wmOperatorType *ot)
{
ot->name = "Open Cache File";
ot->description = "Load a cache file";
ot->idname = "CACHEFILE_OT_open";
ot->invoke = cachefile_open_invoke;
ot->exec = cachefile_open_exec;
ot->cancel = open_cancel;
WM_operator_properties_filesel(ot,
FILE_TYPE_ALEMBIC | FILE_TYPE_USD | FILE_TYPE_FOLDER,
FILE_BLENDER,
FILE_OPENFILE,
WM_FILESEL_FILEPATH | WM_FILESEL_RELPATH,
FILE_DEFAULTDISPLAY,
FILE_SORT_DEFAULT);
}
/* ***************************** Reload Operator **************************** */
static wmOperatorStatus cachefile_reload_exec(bContext *C, wmOperator * /*op*/)
{
CacheFile *cache_file = CTX_data_edit_cachefile(C);
if (cache_file == nullptr) {
return OPERATOR_CANCELLED;
}
reload_cachefile(C, cache_file);
return OPERATOR_FINISHED;
}
void CACHEFILE_OT_reload(wmOperatorType *ot)
{
ot->name = "Refresh Archive";
ot->description = "Update objects paths list with new data from the archive";
ot->idname = "CACHEFILE_OT_reload";
/* API callbacks. */
ot->exec = cachefile_reload_exec;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* ***************************** Add Layer Operator **************************** */
static wmOperatorStatus cachefile_layer_open_invoke(bContext *C,
wmOperator *op,
const wmEvent * /*event*/)
{
if (!RNA_struct_property_is_set(op->ptr, "filepath")) {
char filepath[FILE_MAX];
Main *bmain = CTX_data_main(C);
/* Default to the same directory as the blend file. */
BLI_path_split_dir_part(BKE_main_blendfile_path(bmain), filepath, sizeof(filepath));
RNA_string_set(op->ptr, "filepath", filepath);
}
/* There is no more CacheFile set when returning from the file selector, so store it here. */
op->customdata = CTX_data_edit_cachefile(C);
WM_event_add_fileselect(C, op);
return OPERATOR_RUNNING_MODAL;
}
static wmOperatorStatus cachefile_layer_add_exec(bContext *C, wmOperator *op)
{
if (!RNA_struct_property_is_set(op->ptr, "filepath")) {
BKE_report(op->reports, RPT_ERROR, "No filepath given");
return OPERATOR_CANCELLED;
}
CacheFile *cache_file = static_cast<CacheFile *>(op->customdata);
if (cache_file == nullptr) {
return OPERATOR_CANCELLED;
}
char filepath[FILE_MAX];
RNA_string_get(op->ptr, "filepath", filepath);
CacheFileLayer *layer = BKE_cachefile_add_layer(cache_file, filepath);
if (layer == nullptr) {
WM_global_report(RPT_ERROR, "Could not add a layer to the cache file");
return OPERATOR_CANCELLED;
}
reload_cachefile(C, cache_file);
WM_main_add_notifier(NC_OBJECT | ND_DRAW, nullptr);
return OPERATOR_FINISHED;
}
void CACHEFILE_OT_layer_add(wmOperatorType *ot)
{
ot->name = "Add layer";
ot->description = "Add an override layer to the archive";
ot->idname = "CACHEFILE_OT_layer_add";
/* API callbacks. */
ot->invoke = cachefile_layer_open_invoke;
ot->exec = cachefile_layer_add_exec;
WM_operator_properties_filesel(ot,
FILE_TYPE_ALEMBIC | FILE_TYPE_USD | FILE_TYPE_FOLDER,
FILE_BLENDER,
FILE_OPENFILE,
WM_FILESEL_FILEPATH | WM_FILESEL_RELPATH,
FILE_DEFAULTDISPLAY,
FILE_SORT_DEFAULT);
}
/* ***************************** Remove Layer Operator **************************** */
static wmOperatorStatus cachefile_layer_remove_exec(bContext *C, wmOperator * /*op*/)
{
CacheFile *cache_file = CTX_data_edit_cachefile(C);
if (cache_file == nullptr) {
return OPERATOR_CANCELLED;
}
CacheFileLayer *layer = BKE_cachefile_get_active_layer(cache_file);
BKE_cachefile_remove_layer(cache_file, layer);
reload_cachefile(C, cache_file);
WM_main_add_notifier(NC_OBJECT | ND_DRAW, nullptr);
return OPERATOR_FINISHED;
}
void CACHEFILE_OT_layer_remove(wmOperatorType *ot)
{
ot->name = "Add layer";
ot->description = "Remove an override layer from the archive";
ot->idname = "CACHEFILE_OT_layer_remove";
/* API callbacks. */
ot->exec = cachefile_layer_remove_exec;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* ***************************** Move Layer Operator **************************** */
static wmOperatorStatus cachefile_layer_move_exec(bContext *C, wmOperator *op)
{
CacheFile *cache_file = CTX_data_edit_cachefile(C);
if (cache_file == nullptr) {
return OPERATOR_CANCELLED;
}
CacheFileLayer *layer = BKE_cachefile_get_active_layer(cache_file);
if (layer == nullptr) {
return OPERATOR_CANCELLED;
}
const int dir = RNA_enum_get(op->ptr, "direction");
if (BLI_listbase_link_move(&cache_file->layers, layer, dir)) {
cache_file->active_layer = BLI_findindex(&cache_file->layers, layer) + 1;
/* Only reload if something moved, might be expensive. */
reload_cachefile(C, cache_file);
WM_main_add_notifier(NC_OBJECT | ND_DRAW, nullptr);
}
return OPERATOR_FINISHED;
}
void CACHEFILE_OT_layer_move(wmOperatorType *ot)
{
static const EnumPropertyItem layer_slot_move[] = {
{-1, "UP", 0, "Up", ""},
{1, "DOWN", 0, "Down", ""},
{0, nullptr, 0, nullptr, nullptr},
};
ot->name = "Move layer";
ot->description =
"Move layer in the list, layers further down the list will overwrite data from the layers "
"higher up";
ot->idname = "CACHEFILE_OT_layer_move";
/* API callbacks. */
ot->exec = cachefile_layer_move_exec;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_enum(ot->srna,
"direction",
layer_slot_move,
0,
"Direction",
"Direction to move the active layer towards");
}
} // namespace blender

View File

@@ -0,0 +1,22 @@
/* SPDX-FileCopyrightText: 2016 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup editor/io
*/
namespace blender {
struct wmOperatorType;
void CACHEFILE_OT_open(wmOperatorType *ot);
void CACHEFILE_OT_reload(wmOperatorType *ot);
void CACHEFILE_OT_layer_add(wmOperatorType *ot);
void CACHEFILE_OT_layer_remove(wmOperatorType *ot);
void CACHEFILE_OT_layer_move(wmOperatorType *ot);
} // namespace blender

View File

@@ -0,0 +1,225 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_path_utils.hh"
#include "BLI_string.h"
#include "BLT_translation.hh"
#include "BKE_file_handler.hh"
#include "CLG_log.h"
#include "DNA_space_types.h"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "RNA_prototypes.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "UI_interface.hh"
#include "UI_interface_layout.hh"
#include "io_drop_import_file.hh"
#include "io_utils.hh"
namespace blender {
static CLG_LogRef LOG = {"io.drop_import_file"};
/**
* Return a vector of file handlers that support any file path in `paths` and the call to
* `poll_drop` returns #true. Unlike `bke::file_handlers_poll_file_drop`, it ensures that file
* handlers have a valid import operator.
*/
static Vector<bke::FileHandlerType *> drop_import_file_poll_file_handlers(
const bContext *C, const Span<std::string> paths, const bool quiet = true)
{
auto file_handlers = bke::file_handlers_poll_file_drop(C, paths);
file_handlers.remove_if([quiet](const bke::FileHandlerType *file_handler) {
return WM_operatortype_find(file_handler->import_operator, quiet) == nullptr;
});
return file_handlers;
}
/**
* Sets in the RNA pointer all file paths supported by the file handler.
*/
static void file_handler_import_operator_write_ptr(const bke::FileHandlerType *file_handler,
PointerRNA &props,
const Span<std::string> paths)
{
const auto supported_paths = file_handler->filter_supported_paths(paths);
PropertyRNA *filepath_prop = RNA_struct_find_property_check(props, "filepath", PROP_STRING);
if (filepath_prop) {
RNA_property_string_set(&props, filepath_prop, paths[supported_paths[0]].c_str());
}
PropertyRNA *directory_prop = RNA_struct_find_property_check(props, "directory", PROP_STRING);
char dir[FILE_MAX];
BLI_path_split_dir_part(paths[0].c_str(), dir, sizeof(dir));
if (directory_prop) {
RNA_property_string_set(&props, directory_prop, dir);
}
PropertyRNA *files_prop = RNA_struct_find_collection_property_check(
props, "files", RNA_OperatorFileListElement);
if (files_prop) {
RNA_property_collection_clear(&props, files_prop);
for (const auto &index : supported_paths) {
char file[FILE_MAX];
STRNCPY(file, paths[index].c_str());
BLI_path_rel(file, dir);
PointerRNA item_ptr{};
RNA_property_collection_add(&props, files_prop, &item_ptr);
BLI_assert_msg(BLI_path_is_rel(file), "Expected path to be relative (start with '//')");
RNA_string_set(&item_ptr, "name", file + 2);
}
}
const bool has_any_filepath_prop = filepath_prop || directory_prop || files_prop;
if (!has_any_filepath_prop) {
CLOG_WARN(&LOG,
"The '%s' file handler import operator ('%s') is missing the required operator "
"properties.",
file_handler->idname,
file_handler->import_operator);
}
if (directory_prop && !files_prop) {
CLOG_WARN(
&LOG,
"The '%s' file handler import operator ('%s') is missing the 'files' operator property.",
file_handler->idname,
file_handler->import_operator);
}
if (!directory_prop && files_prop) {
CLOG_WARN(&LOG,
"The '%s' file handler import operator ('%s') is missing the 'directory' operator "
"property.",
file_handler->idname,
file_handler->import_operator);
}
}
static wmOperatorStatus wm_drop_import_file_exec(bContext *C, wmOperator *op)
{
const auto paths = ed::io::paths_from_operator_properties(op->ptr);
if (paths.is_empty()) {
return OPERATOR_CANCELLED;
}
auto file_handlers = drop_import_file_poll_file_handlers(C, paths, false);
if (file_handlers.is_empty()) {
return OPERATOR_CANCELLED;
}
wmOperatorType *ot = WM_operatortype_find(file_handlers[0]->import_operator, false);
PointerRNA file_props = WM_operator_properties_create_ptr(ot);
file_handler_import_operator_write_ptr(file_handlers[0], file_props, paths);
WM_operator_name_call_ptr(C, ot, wm::OpCallContext::InvokeDefault, &file_props, nullptr);
WM_operator_properties_free(&file_props);
return OPERATOR_FINISHED;
}
static wmOperatorStatus wm_drop_import_file_invoke(bContext *C,
wmOperator *op,
const wmEvent * /*event*/)
{
const auto paths = ed::io::paths_from_operator_properties(op->ptr);
if (paths.is_empty()) {
return OPERATOR_CANCELLED;
}
auto file_handlers = drop_import_file_poll_file_handlers(C, paths, false);
if (file_handlers.size() == 1) {
return wm_drop_import_file_exec(C, op);
}
/**
* Create a menu with all file handler import operators that can support any files in paths and
* let user decide which to use.
*/
ui::PopupMenu *pup = ui::popup_menu_begin(C, "", ICON_NONE);
ui::Layout &layout = *popup_menu_layout(pup);
layout.operator_context_set(wm::OpCallContext::InvokeDefault);
for (auto *file_handler : file_handlers) {
wmOperatorType *ot = WM_operatortype_find(file_handler->import_operator, false);
PointerRNA file_props = layout.op(ot,
CTX_TIP_(ot->translation_context, ot->name),
ICON_NONE,
wm::OpCallContext::InvokeDefault,
UI_ITEM_NONE);
file_handler_import_operator_write_ptr(file_handler, file_props, paths);
}
popup_menu_end(C, pup);
return OPERATOR_INTERFACE;
}
void WM_OT_drop_import_file(wmOperatorType *ot)
{
ot->name = "Drop to Import File";
ot->description = "Operator that allows file handlers to receive file drops";
ot->idname = "WM_OT_drop_import_file";
ot->flag = OPTYPE_INTERNAL;
ot->exec = wm_drop_import_file_exec;
ot->invoke = wm_drop_import_file_invoke;
PropertyRNA *prop;
prop = RNA_def_string_dir_path(
ot->srna, "directory", nullptr, FILE_MAX, "Directory", "Directory of the file");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_collection_runtime(ot->srna, "files", RNA_OperatorFileListElement, "Files", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
}
static void drop_import_file_copy(bContext * /*C*/, wmDrag *drag, wmDropBox *drop)
{
ed::io::paths_to_operator_properties(drop->ptr, WM_drag_get_paths(drag));
}
static bool drop_import_file_poll(bContext *C, wmDrag *drag, const wmEvent * /*event*/)
{
if (drag->type != WM_DRAG_PATH) {
return false;
}
const auto paths = WM_drag_get_paths(drag);
return !drop_import_file_poll_file_handlers(C, paths, true).is_empty();
}
static std::string drop_import_file_tooltip(bContext *C,
wmDrag *drag,
const int /*xy*/[2],
wmDropBox * /*drop*/)
{
const auto paths = WM_drag_get_paths(drag);
const auto file_handlers = drop_import_file_poll_file_handlers(C, paths, true);
if (file_handlers.size() == 1) {
wmOperatorType *ot = WM_operatortype_find(file_handlers[0]->import_operator, false);
return TIP_(ot->name);
}
return TIP_("Multiple file handlers can be used, drop to pick which to use");
}
void ED_dropbox_drop_import_file()
{
ListBaseT<wmDropBox> *lb = WM_dropboxmap_find("Window", SPACE_EMPTY, RGN_TYPE_WINDOW);
WM_dropbox_add(lb,
"WM_OT_drop_import_file",
drop_import_file_poll,
drop_import_file_copy,
nullptr,
drop_import_file_tooltip);
}
} // namespace blender

View File

@@ -0,0 +1,14 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
namespace blender {
struct wmOperatorType;
void WM_OT_drop_import_file(wmOperatorType *ot);
void ED_dropbox_drop_import_file();
} // namespace blender

View File

@@ -0,0 +1,265 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup editor/io
*/
#ifdef WITH_IO_FBX
# include "BKE_context.hh"
# include "BKE_file_handler.hh"
# include "BKE_report.hh"
# include "BLI_string.h"
# include "BLI_string_utf8.h"
# include "WM_api.hh"
# include "DNA_space_types.h"
# include "ED_outliner.hh"
# include "RNA_access.hh"
# include "RNA_define.hh"
# include "BLT_translation.hh"
# include "UI_interface.hh"
# include "UI_interface_layout.hh"
# include "IO_fbx.hh"
# include "io_fbx_ops.hh"
# include "io_utils.hh"
namespace blender {
const EnumPropertyItem rna_enum_fbx_mtl_name_collision_mode_items[] = {
{int(eFBXMtlNameCollisionMode::MakeUnique),
"MAKE_UNIQUE",
0,
"Make Unique",
"Import each FBX material as a unique Blender material"},
{int(eFBXMtlNameCollisionMode::ReferenceExisting),
"REFERENCE_EXISTING",
0,
"Reference Existing",
"If a material with the same name already exists, reference that instead of importing"},
{0, nullptr, 0, nullptr, nullptr},
};
static const EnumPropertyItem fbx_vertex_colors_mode[] = {
{int(eFBXVertexColorMode::None), "NONE", 0, "None", "Do not import color attributes"},
{int(eFBXVertexColorMode::sRGB),
"SRGB",
0,
"sRGB",
"Vertex colors in the file are in sRGB color space"},
{int(eFBXVertexColorMode::Linear),
"LINEAR",
0,
"Linear",
"Vertex colors in the file are in linear color space"},
{0, nullptr, 0, nullptr, nullptr}};
static wmOperatorStatus wm_fbx_import_exec(bContext *C, wmOperator *op)
{
FBXImportParams params;
params.global_scale = RNA_float_get(op->ptr, "global_scale");
params.use_custom_normals = RNA_boolean_get(op->ptr, "use_custom_normals");
params.use_custom_props = RNA_boolean_get(op->ptr, "use_custom_props");
params.props_enum_as_string = RNA_boolean_get(op->ptr, "use_custom_props_enum_as_string");
params.ignore_leaf_bones = RNA_boolean_get(op->ptr, "ignore_leaf_bones");
params.import_subdivision = RNA_boolean_get(op->ptr, "import_subdivision");
params.validate_meshes = RNA_boolean_get(op->ptr, "validate_meshes");
params.use_anim = RNA_boolean_get(op->ptr, "use_anim");
params.anim_offset = RNA_float_get(op->ptr, "anim_offset");
params.vertex_colors = eFBXVertexColorMode(RNA_enum_get(op->ptr, "import_colors"));
params.mtl_name_collision_mode = eFBXMtlNameCollisionMode(
RNA_enum_get(op->ptr, "mtl_name_collision_mode"));
params.reports = op->reports;
const auto paths = ed::io::paths_from_operator_properties(op->ptr);
if (paths.is_empty()) {
BKE_report(op->reports, RPT_ERROR, "No filepath given");
return OPERATOR_CANCELLED;
}
for (const auto &path : paths) {
STRNCPY(params.filepath, path.c_str());
FBX_import(C, params);
}
Scene *scene = CTX_data_scene(C);
WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene);
WM_event_add_notifier(C, NC_SCENE | ND_OB_ACTIVE, scene);
WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene);
ED_outliner_select_sync_from_object_tag(C);
return OPERATOR_FINISHED;
}
static bool wm_fbx_import_check(bContext * /*C*/, wmOperator * /*op*/)
{
return false;
}
static void ui_fbx_import_settings(const bContext *C, ui::Layout &layout, PointerRNA *ptr)
{
layout.use_property_split_set(true);
layout.use_property_decorate_set(false);
if (ui::Layout *panel = layout.panel(C, "FBX_import_general", false, IFACE_("General"))) {
ui::Layout &col = panel->column(false);
col.prop(ptr, "global_scale", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col.prop(ptr, "use_custom_props", UI_ITEM_NONE, std::nullopt, ICON_NONE);
ui::Layout &subcol = col.column(false);
subcol.active_set(RNA_boolean_get(ptr, "use_custom_props"));
subcol.prop(ptr, "use_custom_props_enum_as_string", UI_ITEM_NONE, std::nullopt, ICON_NONE);
}
if (ui::Layout *panel = layout.panel(C, "FBX_import_geometry", false, IFACE_("Geometry"))) {
ui::Layout &col = panel->column(false);
col.prop(ptr, "use_custom_normals", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col.prop(ptr, "import_subdivision", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col.prop(ptr, "import_colors", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col.prop(ptr, "validate_meshes", UI_ITEM_NONE, std::nullopt, ICON_NONE);
}
if (ui::Layout *panel = layout.panel(C, "FBX_import_material", true, IFACE_("Materials"))) {
ui::Layout &col = panel->column(false);
col.prop(ptr, "mtl_name_collision_mode", UI_ITEM_NONE, std::nullopt, ICON_NONE);
}
{
ui::PanelLayout panel = layout.panel(C, "FBX_import_anim", true);
panel.header->use_property_split_set(false);
panel.header->prop(ptr, "use_anim", UI_ITEM_NONE, "", ICON_NONE);
panel.header->label(IFACE_("Animation"), ICON_NONE);
if (panel.body) {
ui::Layout &col = panel.body->column(false);
col.prop(ptr, "anim_offset", UI_ITEM_NONE, std::nullopt, ICON_NONE);
}
}
if (ui::Layout *panel = layout.panel(C, "FBX_import_armature", false, IFACE_("Armature"))) {
ui::Layout &col = panel->column(false);
col.prop(ptr, "ignore_leaf_bones", UI_ITEM_NONE, std::nullopt, ICON_NONE);
}
}
static void wm_fbx_import_draw(bContext *C, wmOperator *op)
{
ui_fbx_import_settings(C, *op->layout, op->ptr);
}
void WM_OT_fbx_import(wmOperatorType *ot)
{
PropertyRNA *prop;
ot->name = "Import FBX";
ot->description = "Import FBX file into current scene";
ot->idname = "WM_OT_fbx_import";
ot->invoke = ed::io::filesel_drop_import_invoke;
ot->exec = wm_fbx_import_exec;
ot->poll = WM_operator_winactive;
ot->check = wm_fbx_import_check;
ot->ui = wm_fbx_import_draw;
ot->flag = OPTYPE_UNDO | OPTYPE_PRESET;
WM_operator_properties_filesel(ot,
FILE_TYPE_FOLDER,
FILE_BLENDER,
FILE_OPENFILE,
WM_FILESEL_FILEPATH | WM_FILESEL_FILES | WM_FILESEL_DIRECTORY |
WM_FILESEL_SHOW_PROPS,
FILE_DEFAULTDISPLAY,
FILE_SORT_DEFAULT);
RNA_def_float(ot->srna, "global_scale", 1.0f, 1e-6f, 1e6f, "Scale", "", 0.001f, 1000.0f);
RNA_def_enum(
ot->srna,
"mtl_name_collision_mode",
rna_enum_fbx_mtl_name_collision_mode_items,
int(eFBXMtlNameCollisionMode::MakeUnique),
"Material Name Collision",
"Behavior when the name of an imported material conflicts with an existing material");
RNA_def_enum(ot->srna,
"import_colors",
fbx_vertex_colors_mode,
int(eFBXVertexColorMode::sRGB),
"Vertex Colors",
"Import vertex color attributes");
RNA_def_boolean(ot->srna,
"use_custom_normals",
true,
"Custom Normals",
"Import custom normals, if available (otherwise Blender will compute them)");
RNA_def_boolean(ot->srna,
"use_custom_props",
true,
"Custom Properties",
"Import user properties as custom properties");
RNA_def_boolean(ot->srna,
"use_custom_props_enum_as_string",
true,
"Enums As Strings",
"Store custom property enumeration values as strings");
RNA_def_boolean(ot->srna,
"import_subdivision",
false,
"Subdivision Data",
"Import FBX subdivision information as subdivision surface modifiers");
RNA_def_boolean(ot->srna,
"ignore_leaf_bones",
false,
"Ignore Leaf Bones",
"Ignore the last bone at the end of each chain (used to mark the length of the "
"previous bone)");
RNA_def_boolean(
ot->srna,
"validate_meshes",
true,
"Validate Meshes",
"Ensure the data is valid "
"(when disabled, data may be imported which causes crashes displaying or editing)");
RNA_def_boolean(ot->srna, "use_anim", true, "Import Animation", "Import FBX animation");
prop = RNA_def_float(ot->srna,
"anim_offset",
1.0f,
-1e6f,
1e6f,
"Offset",
"Offset to apply to animation timestamps, in frames",
-1e4f,
1e4f);
RNA_def_property_ui_range(prop, -1e4f, 1e4f, 100, 1);
/* Only show `.fbx` files by default. */
prop = RNA_def_string(ot->srna, "filter_glob", "*.fbx", 0, "Extension Filter", "");
RNA_def_property_flag(prop, PROP_HIDDEN);
}
namespace ed::io {
void fbx_file_handler_add()
{
auto fh = std::make_unique<bke::FileHandlerType>();
STRNCPY_UTF8(fh->idname, "IO_FH_fbx");
STRNCPY_UTF8(fh->import_operator, "WM_OT_fbx_import");
STRNCPY_UTF8(fh->export_operator, "export_scene.fbx"); /* Use Python add-on for export. */
STRNCPY_UTF8(fh->label, "FBX");
STRNCPY_UTF8(fh->file_extensions_str, ".fbx");
fh->poll_drop = poll_file_object_drop;
bke::file_handler_add(std::move(fh));
}
} // namespace ed::io
} // namespace blender
#endif /* WITH_IO_FBX */

View File

@@ -0,0 +1,21 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup editor/io
*/
#pragma once
namespace blender {
struct wmOperatorType;
void WM_OT_fbx_import(wmOperatorType *ot);
namespace ed::io {
void fbx_file_handler_add();
}
} // namespace blender

View File

@@ -0,0 +1,624 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup editor/io
*/
#ifdef WITH_IO_GREASE_PENCIL
# include "BLI_path_utils.hh"
# include "BLI_string.h"
# include "BLI_string_utf8.h"
# include "DNA_space_types.h"
# include "DNA_view3d_types.h"
# include "BKE_context.hh"
# include "BKE_file_handler.hh"
# include "BKE_report.hh"
# include "BKE_screen.hh"
# include "BLT_translation.hh"
# include "RNA_access.hh"
# include "RNA_define.hh"
# include "ED_fileselect.hh"
# include "UI_interface.hh"
# include "UI_interface_layout.hh"
# include "UI_resources.hh"
# include "WM_api.hh"
# include "WM_types.hh"
# include "io_grease_pencil.hh"
# include "io_utils.hh"
# include "grease_pencil_io.hh"
namespace blender {
namespace ed::io {
# if defined(WITH_PUGIXML) || defined(WITH_HARU)
/* Definition of enum elements to export. */
/* Common props for exporting. */
static void grease_pencil_export_common_props_definition(wmOperatorType *ot)
{
using blender::io::grease_pencil::ExportParams;
using SelectMode = ExportParams::SelectMode;
using FrameMode = ExportParams::FrameMode;
static const EnumPropertyItem select_mode_items[] = {
{int(SelectMode::Active), "ACTIVE", 0, "Active", "Include only the active object"},
{int(SelectMode::Selected), "SELECTED", 0, "Selected", "Include selected objects"},
{int(SelectMode::Visible), "VISIBLE", 0, "Visible", "Include all visible objects"},
{0, nullptr, 0, nullptr, nullptr},
};
static const EnumPropertyItem frame_mode_items[] = {
{int(FrameMode::Active), "ACTIVE", 0, "Active", "Include only active frame"},
{int(FrameMode::Selected), "SELECTED", 0, "Selected", "Include selected frames"},
{int(FrameMode::Scene), "SCENE", 0, "Scene", "Include all scene frames"},
{0, nullptr, 0, nullptr, nullptr},
};
RNA_def_boolean(ot->srna, "use_fill", true, "Fill", "Export strokes with fill enabled");
RNA_def_enum(ot->srna,
"selected_object_type",
select_mode_items,
int(SelectMode::Active),
"Object",
"Which objects to include in the export");
ot->prop = RNA_def_enum(ot->srna,
"frame_mode",
frame_mode_items,
int(FrameMode::Active),
"Frames",
"Which frames to include in the export");
RNA_def_float(ot->srna,
"stroke_sample",
0.0f,
0.0f,
100.0f,
"Sampling",
"Precision of stroke sampling. Low values mean a more precise result, and zero "
"disables sampling",
0.0f,
100.0f);
RNA_def_boolean(
ot->srna, "use_uniform_width", false, "Uniform Width", "Export strokes with uniform width");
}
# endif
/* Note: Region data is found using "big area" functions, rather than context. This is necessary
* since export operators are not always invoked from a View3D. This enables the operator to find
* the most relevant 3D view for projection of strokes. */
static bool get_invoke_region(bContext *C,
ARegion **r_region,
View3D **r_view3d,
RegionView3D **r_rv3d)
{
bScreen *screen = CTX_wm_screen(C);
if (screen == nullptr) {
return false;
}
ScrArea *area = BKE_screen_find_big_area(screen, SPACE_VIEW3D, 0);
if (area == nullptr) {
return false;
}
ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW);
*r_region = region;
*r_view3d = static_cast<View3D *>(area->spacedata.first);
*r_rv3d = static_cast<RegionView3D *>(region->regiondata);
return true;
}
} // namespace ed::io
/* -------------------------------------------------------------------- */
/** \name SVG single frame import
* \{ */
namespace ed::io {
static bool grease_pencil_import_svg_check(bContext * /*C*/, wmOperator *op)
{
char filepath[FILE_MAX];
RNA_string_get(op->ptr, "filepath", filepath);
if (!BLI_path_extension_check(filepath, ".svg")) {
BLI_path_extension_ensure(filepath, FILE_MAX, ".svg");
RNA_string_set(op->ptr, "filepath", filepath);
return true;
}
return false;
}
static wmOperatorStatus grease_pencil_import_svg_exec(bContext *C, wmOperator *op)
{
using blender::io::grease_pencil::ImportParams;
using blender::io::grease_pencil::IOContext;
Scene *scene = CTX_data_scene(C);
if (!RNA_struct_property_is_set_ex(op->ptr, "filepath", false) ||
!RNA_struct_find_property(op->ptr, "directory"))
{
BKE_report(op->reports, RPT_ERROR, "No filepath given");
return OPERATOR_CANCELLED;
}
ARegion *region;
View3D *v3d;
RegionView3D *rv3d;
if (!get_invoke_region(C, &region, &v3d, &rv3d)) {
BKE_report(op->reports, RPT_ERROR, "Unable to find valid 3D View area");
return OPERATOR_CANCELLED;
}
const int resolution = RNA_int_get(op->ptr, "resolution");
const float scale = RNA_float_get(op->ptr, "scale");
const bool use_scene_unit = RNA_boolean_get(op->ptr, "use_scene_unit");
const bool recenter_bounds = RNA_boolean_get(op->ptr, "recenter_bounds");
const IOContext io_context(*C, region, v3d, rv3d, op->reports);
const ImportParams params = {scale, scene->r.cfra, resolution, use_scene_unit, recenter_bounds};
/* Loop all selected files to import them. All SVG imported shared the same import
* parameters, but they are created in separated grease pencil objects. */
const auto paths = ed::io::paths_from_operator_properties(op->ptr);
for (const auto &path : paths) {
/* Do Import. */
WM_cursor_wait(true);
const bool done = blender::io::grease_pencil::import_svg(io_context, params, path);
WM_cursor_wait(false);
if (!done) {
BKE_reportf(op->reports, RPT_WARNING, "Unable to import '%s'", path.c_str());
}
}
return OPERATOR_FINISHED;
}
static void grease_pencil_import_svg_draw(bContext * /*C*/, wmOperator *op)
{
ui::Layout &layout = *op->layout;
layout.use_property_split_set(true);
layout.use_property_decorate_set(false);
ui::Layout &col = layout.box().column(false);
col.prop(op->ptr, "resolution", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col.prop(op->ptr, "scale", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col.prop(op->ptr, "use_scene_unit", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col.prop(op->ptr, "recenter_bounds", UI_ITEM_NONE, std::nullopt, ICON_NONE);
}
static bool grease_pencil_import_svg_poll(bContext *C)
{
if ((CTX_wm_window(C) == nullptr) || (CTX_data_mode_enum(C) != CTX_MODE_OBJECT)) {
return false;
}
return true;
}
} // namespace ed::io
void WM_OT_grease_pencil_import_svg(wmOperatorType *ot)
{
ot->name = "Import SVG as Grease Pencil";
ot->description = "Import SVG into Grease Pencil";
ot->idname = "WM_OT_grease_pencil_import_svg";
ot->invoke = ed::io::filesel_drop_import_invoke;
ot->exec = ed::io::grease_pencil_import_svg_exec;
ot->poll = ed::io::grease_pencil_import_svg_poll;
ot->ui = ed::io::grease_pencil_import_svg_draw;
ot->check = ed::io::grease_pencil_import_svg_check;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_PRESET;
WM_operator_properties_filesel(ot,
FILE_TYPE_FOLDER | FILE_TYPE_OBJECT_IO,
FILE_BLENDER,
FILE_OPENFILE,
WM_FILESEL_FILEPATH | WM_FILESEL_RELPATH | WM_FILESEL_SHOW_PROPS |
WM_FILESEL_DIRECTORY | WM_FILESEL_FILES,
FILE_DEFAULTDISPLAY,
FILE_SORT_DEFAULT);
RNA_def_int(ot->srna,
"resolution",
10,
1,
100000,
"Resolution",
"Resolution of the generated strokes",
1,
20);
RNA_def_float(ot->srna,
"scale",
10.0f,
0.000001f,
1000000.0f,
"Scale",
"Scale of the final strokes",
0.001f,
100.0f);
RNA_def_boolean(ot->srna,
"use_scene_unit",
false,
"Scene Unit",
"Apply current scene's unit (as defined by unit scale) to imported data");
RNA_def_boolean(ot->srna,
"recenter_bounds",
true,
"Center on Bounds",
"Center the imported SVG on its bounding box");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name SVG single frame export
* \{ */
# ifdef WITH_PUGIXML
namespace ed::io {
static bool grease_pencil_export_svg_check(bContext * /*C*/, wmOperator *op)
{
char filepath[FILE_MAX];
RNA_string_get(op->ptr, "filepath", filepath);
if (!BLI_path_extension_check(filepath, ".svg")) {
BLI_path_extension_ensure(filepath, FILE_MAX, ".svg");
RNA_string_set(op->ptr, "filepath", filepath);
return true;
}
return false;
}
static wmOperatorStatus grease_pencil_export_svg_invoke(bContext *C,
wmOperator *op,
const wmEvent * /*event*/)
{
ED_fileselect_ensure_default_filepath(C, op, ".svg");
WM_event_add_fileselect(C, op);
return OPERATOR_RUNNING_MODAL;
}
static wmOperatorStatus grease_pencil_export_svg_exec(bContext *C, wmOperator *op)
{
using blender::io::grease_pencil::ExportParams;
using blender::io::grease_pencil::ExportStatus;
using blender::io::grease_pencil::IOContext;
Scene *scene = CTX_data_scene(C);
Object *ob = CTX_data_active_object(C);
if (!RNA_struct_property_is_set_ex(op->ptr, "filepath", false)) {
BKE_report(op->reports, RPT_ERROR, "No filepath given");
return OPERATOR_CANCELLED;
}
ARegion *region;
View3D *v3d;
RegionView3D *rv3d;
if (!get_invoke_region(C, &region, &v3d, &rv3d)) {
BKE_report(op->reports, RPT_ERROR, "Unable to find valid 3D View area");
return OPERATOR_CANCELLED;
}
char filepath[FILE_MAX];
RNA_string_get(op->ptr, "filepath", filepath);
const bool export_stroke_materials = true;
const bool export_fill_materials = RNA_boolean_get(op->ptr, "use_fill");
const bool use_uniform_width = RNA_boolean_get(op->ptr, "use_uniform_width");
const ExportParams::SelectMode select_mode = ExportParams::SelectMode(
RNA_enum_get(op->ptr, "selected_object_type"));
const ExportParams::FrameMode frame_mode = ExportParams::FrameMode(
RNA_enum_get(op->ptr, "frame_mode"));
const bool use_clip_camera = RNA_boolean_get(op->ptr, "use_clip_camera");
const float stroke_sample = RNA_float_get(op->ptr, "stroke_sample");
const IOContext io_context(*C, region, v3d, rv3d, op->reports);
const ExportParams params = {ob,
select_mode,
frame_mode,
export_stroke_materials,
export_fill_materials,
use_clip_camera,
use_uniform_width,
stroke_sample};
WM_cursor_wait(true);
ExportStatus status = blender::io::grease_pencil::export_svg(
io_context, params, *scene, filepath);
WM_cursor_wait(false);
switch (status) {
case ExportStatus::Ok:
break;
case ExportStatus::InvalidActiveObjectType:
BKE_report(op->reports, RPT_WARNING, "Active object is not a Grease Pencil object");
break;
case ExportStatus::NoFramesSelected:
BKE_report(op->reports, RPT_WARNING, "No frames selected in the Grease Pencil object");
break;
case ExportStatus::FileWriteError:
BKE_reportf(op->reports, RPT_WARNING, "Error during file write for \"%s\"", filepath);
break;
case ExportStatus::UnknownError:
BLI_assert_unreachable();
break;
}
return OPERATOR_FINISHED;
}
enum class GreasePencilExportFiletype {
SVG = 0,
PDF = 1,
};
/**
* Defines the layout of the exporter properties.
*
* \param ptr: RNA pointer to access the export operator's properties.
*/
static void ui_gpencil_export_settings(ui::Layout &layout,
PointerRNA *ptr,
GreasePencilExportFiletype file_type)
{
layout.use_property_split_set(true);
layout.use_property_decorate_set(false);
ui::Layout *box = &layout.box();
ui::Layout *row = &box->row(false);
row->label(IFACE_("Scene Options"), ICON_NONE);
row = &box->row(false);
row->prop(ptr, "selected_object_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
box = &layout.box();
row = &box->row(false);
row->label(IFACE_("Export Options"), ICON_NONE);
ui::Layout &col = box->column(false);
ui::Layout *sub = &col.column(false);
sub->prop(ptr, "frame_mode", UI_ITEM_NONE, IFACE_("Frame"), ICON_NONE);
box->use_property_split_set(true);
sub = &col.column(true);
sub->prop(ptr, "stroke_sample", UI_ITEM_NONE, std::nullopt, ICON_NONE);
sub->prop(ptr, "use_fill", UI_ITEM_NONE, std::nullopt, ICON_NONE);
sub->prop(ptr, "use_uniform_width", UI_ITEM_NONE, std::nullopt, ICON_NONE);
if (file_type == GreasePencilExportFiletype::SVG) {
col.prop(ptr, "use_clip_camera", UI_ITEM_NONE, std::nullopt, ICON_NONE);
}
}
static void grease_pencil_export_svg_draw(bContext * /*C*/, wmOperator *op)
{
ui_gpencil_export_settings(*op->layout, op->ptr, GreasePencilExportFiletype::SVG);
}
static bool grease_pencil_export_svg_poll(bContext *C)
{
if ((CTX_wm_window(C) == nullptr) || (CTX_data_mode_enum(C) != CTX_MODE_OBJECT)) {
return false;
}
return true;
}
} // namespace ed::io
void WM_OT_grease_pencil_export_svg(wmOperatorType *ot)
{
ot->name = "Export to SVG";
ot->description = "Export Grease Pencil to SVG";
ot->idname = "WM_OT_grease_pencil_export_svg";
ot->invoke = ed::io::grease_pencil_export_svg_invoke;
ot->exec = ed::io::grease_pencil_export_svg_exec;
ot->poll = ed::io::grease_pencil_export_svg_poll;
ot->ui = ed::io::grease_pencil_export_svg_draw;
ot->check = ed::io::grease_pencil_export_svg_check;
ot->flag = OPTYPE_PRESET;
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_DEFAULT);
ed::io::grease_pencil_export_common_props_definition(ot);
RNA_def_boolean(ot->srna,
"use_clip_camera",
false,
"Clip Camera",
"Clip drawings to camera size when exporting in camera view");
}
# endif /* WITH_PUGIXML */
/** \} */
/* -------------------------------------------------------------------- */
/** \name PDF single frame export
* \{ */
# ifdef WITH_HARU
namespace ed::io {
static bool grease_pencil_export_pdf_check(bContext * /*C*/, wmOperator *op)
{
char filepath[FILE_MAX];
RNA_string_get(op->ptr, "filepath", filepath);
if (!BLI_path_extension_check(filepath, ".pdf")) {
BLI_path_extension_ensure(filepath, FILE_MAX, ".pdf");
RNA_string_set(op->ptr, "filepath", filepath);
return true;
}
return false;
}
static wmOperatorStatus grease_pencil_export_pdf_invoke(bContext *C,
wmOperator *op,
const wmEvent * /*event*/)
{
ED_fileselect_ensure_default_filepath(C, op, ".pdf");
WM_event_add_fileselect(C, op);
return OPERATOR_RUNNING_MODAL;
}
static wmOperatorStatus grease_pencil_export_pdf_exec(bContext *C, wmOperator *op)
{
using blender::io::grease_pencil::ExportParams;
using blender::io::grease_pencil::IOContext;
Scene *scene = CTX_data_scene(C);
Object *ob = CTX_data_active_object(C);
if (!RNA_struct_property_is_set_ex(op->ptr, "filepath", false)) {
BKE_report(op->reports, RPT_ERROR, "No filepath given");
return OPERATOR_CANCELLED;
}
ARegion *region;
View3D *v3d;
RegionView3D *rv3d;
if (!get_invoke_region(C, &region, &v3d, &rv3d)) {
BKE_report(op->reports, RPT_ERROR, "Unable to find valid 3D View area");
return OPERATOR_CANCELLED;
}
char filepath[FILE_MAX];
RNA_string_get(op->ptr, "filepath", filepath);
const bool export_stroke_materials = true;
const bool export_fill_materials = RNA_boolean_get(op->ptr, "use_fill");
const bool use_uniform_width = RNA_boolean_get(op->ptr, "use_uniform_width");
const ExportParams::SelectMode select_mode = ExportParams::SelectMode(
RNA_enum_get(op->ptr, "selected_object_type"));
const ExportParams::FrameMode frame_mode = ExportParams::FrameMode(
RNA_enum_get(op->ptr, "frame_mode"));
const bool use_clip_camera = false;
const float stroke_sample = RNA_float_get(op->ptr, "stroke_sample");
const IOContext io_context(*C, region, v3d, rv3d, op->reports);
const ExportParams params = {ob,
select_mode,
frame_mode,
export_stroke_materials,
export_fill_materials,
use_clip_camera,
use_uniform_width,
stroke_sample};
WM_cursor_wait(true);
const bool done = blender::io::grease_pencil::export_pdf(io_context, params, *scene, filepath);
WM_cursor_wait(false);
if (!done) {
BKE_report(op->reports, RPT_WARNING, "Unable to export PDF");
}
return OPERATOR_FINISHED;
}
static void grease_pencil_export_pdf_draw(bContext * /*C*/, wmOperator *op)
{
ui_gpencil_export_settings(*op->layout, op->ptr, GreasePencilExportFiletype::PDF);
}
static bool grease_pencil_export_pdf_poll(bContext *C)
{
if ((CTX_wm_window(C) == nullptr) || (CTX_data_mode_enum(C) != CTX_MODE_OBJECT)) {
return false;
}
return true;
}
} // namespace ed::io
void WM_OT_grease_pencil_export_pdf(wmOperatorType *ot)
{
ot->name = "Export to PDF";
ot->description = "Export Grease Pencil to PDF";
ot->idname = "WM_OT_grease_pencil_export_pdf";
ot->invoke = ed::io::grease_pencil_export_pdf_invoke;
ot->exec = ed::io::grease_pencil_export_pdf_exec;
ot->poll = ed::io::grease_pencil_export_pdf_poll;
ot->ui = ed::io::grease_pencil_export_pdf_draw;
ot->check = ed::io::grease_pencil_export_pdf_check;
ot->flag = OPTYPE_PRESET;
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_DEFAULT);
using blender::io::grease_pencil::ExportParams;
ed::io::grease_pencil_export_common_props_definition(ot);
}
# endif /* WITH_HARU */
/** \} */
namespace ed::io {
void grease_pencil_file_handler_add()
{
auto fh = std::make_unique<bke::FileHandlerType>();
STRNCPY_UTF8(fh->idname, "IO_FH_grease_pencil_svg");
STRNCPY_UTF8(fh->import_operator, "WM_OT_grease_pencil_import_svg");
STRNCPY_UTF8(fh->label, "SVG as Grease Pencil");
STRNCPY_UTF8(fh->file_extensions_str, ".svg");
fh->poll_drop = poll_file_object_drop;
bke::file_handler_add(std::move(fh));
}
} // namespace ed::io
} // namespace blender
#endif /* WITH_IO_GREASE_PENCIL */

View File

@@ -0,0 +1,28 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup editor/io
*/
#pragma once
namespace blender {
struct wmOperatorType;
void WM_OT_grease_pencil_import_svg(wmOperatorType *ot);
#ifdef WITH_PUGIXML
void WM_OT_grease_pencil_export_svg(wmOperatorType *ot);
#endif
#ifdef WITH_HARU
void WM_OT_grease_pencil_export_pdf(wmOperatorType *ot);
#endif
namespace ed::io {
void grease_pencil_file_handler_add();
}
} // namespace blender

View File

@@ -0,0 +1,616 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup editor/io
*/
#ifdef WITH_IO_WAVEFRONT_OBJ
# include "DNA_space_types.h"
# include "BKE_context.hh"
# include "BKE_file_handler.hh"
# include "BKE_main.hh"
# include "BKE_report.hh"
# include "BLI_path_utils.hh"
# include "BLI_string.h"
# include "BLI_string_utf8.h"
# include "BLT_translation.hh"
# include "ED_fileselect.hh"
# include "ED_outliner.hh"
# include "RNA_access.hh"
# include "RNA_define.hh"
# include "UI_interface.hh"
# include "UI_interface_layout.hh"
# include "UI_resources.hh"
# include "WM_api.hh"
# include "WM_types.hh"
# include "DEG_depsgraph.hh"
# include "IO_orientation.hh"
# include "IO_path_util_types.hh"
# include "IO_wavefront_obj.hh"
# include "io_obj.hh"
# include "io_utils.hh"
namespace blender {
static 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",
"Export objects as they appear in the viewport"},
{0, nullptr, 0, nullptr, nullptr}};
static const EnumPropertyItem io_obj_path_mode[] = {
{PATH_REFERENCE_AUTO, "AUTO", 0, "Auto", "Use relative paths with subdirectories only"},
{PATH_REFERENCE_ABSOLUTE, "ABSOLUTE", 0, "Absolute", "Always write absolute paths"},
{PATH_REFERENCE_RELATIVE, "RELATIVE", 0, "Relative", "Write relative paths where possible"},
{PATH_REFERENCE_MATCH, "MATCH", 0, "Match", "Match absolute/relative setting with input path"},
{PATH_REFERENCE_STRIP, "STRIP", 0, "Strip", "Write filename only"},
{PATH_REFERENCE_COPY, "COPY", 0, "Copy", "Copy the file to the destination path"},
{0, nullptr, 0, nullptr, nullptr}};
static const EnumPropertyItem io_obj_mtl_name_collision_mode[] = {
{OBJ_MTL_NAME_COLLISION_MAKE_UNIQUE,
"MAKE_UNIQUE",
0,
"Make Unique",
"Create new materials with unique names for each OBJ file"},
{OBJ_MTL_NAME_COLLISION_REFERENCE_EXISTING,
"REFERENCE_EXISTING",
0,
"Reference Existing",
"Use existing materials with same name instead of creating new ones"},
{0, nullptr, 0, nullptr, nullptr}};
static wmOperatorStatus wm_obj_export_invoke(bContext *C,
wmOperator *op,
const wmEvent * /*event*/)
{
ED_fileselect_ensure_default_filepath(C, op, ".obj");
WM_event_add_fileselect(C, op);
return OPERATOR_RUNNING_MODAL;
}
static wmOperatorStatus wm_obj_export_exec(bContext *C, wmOperator *op)
{
if (!RNA_struct_property_is_set_ex(op->ptr, "filepath", false)) {
BKE_report(op->reports, RPT_ERROR, "No filepath given");
return OPERATOR_CANCELLED;
}
OBJExportParams export_params;
export_params.file_base_for_tests[0] = '\0';
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 = eIOAxis(RNA_enum_get(op->ptr, "forward_axis"));
export_params.up_axis = eIOAxis(RNA_enum_get(op->ptr, "up_axis"));
export_params.global_scale = RNA_float_get(op->ptr, "global_scale");
export_params.apply_modifiers = RNA_boolean_get(op->ptr, "apply_modifiers");
export_params.apply_transform = RNA_boolean_get(op->ptr, "apply_transform");
export_params.export_eval_mode = eEvaluationMode(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_colors = RNA_boolean_get(op->ptr, "export_colors");
export_params.export_materials = RNA_boolean_get(op->ptr, "export_materials");
export_params.path_mode = ePathReferenceMode(RNA_enum_get(op->ptr, "path_mode"));
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_pbr_extensions = RNA_boolean_get(op->ptr, "export_pbr_extensions");
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");
export_params.reports = op->reports;
RNA_string_get(op->ptr, "collection", export_params.collection);
OBJ_export(C, &export_params);
if (BKE_reports_contain(op->reports, RPT_ERROR)) {
return OPERATOR_CANCELLED;
}
BKE_report(op->reports, RPT_INFO, "File exported successfully");
return OPERATOR_FINISHED;
}
static void ui_obj_export_settings(const bContext *C, ui::Layout &layout, PointerRNA *ptr)
{
const bool export_animation = RNA_boolean_get(ptr, "export_animation");
const bool export_smooth_groups = RNA_boolean_get(ptr, "export_smooth_groups");
const bool export_materials = RNA_boolean_get(ptr, "export_materials");
layout.use_property_split_set(true);
layout.use_property_decorate_set(false);
/* Object General options. */
if (ui::Layout *panel = layout.panel(C, "OBJ_export_general", false, IFACE_("General"))) {
ui::Layout &col = panel->column(false);
if (CTX_wm_space_file(C)) {
ui::Layout &sub = col.column(false, IFACE_("Include"));
sub.prop(ptr, "export_selected_objects", UI_ITEM_NONE, IFACE_("Selection Only"), ICON_NONE);
}
col.prop(ptr, "global_scale", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col.prop(ptr, "forward_axis", UI_ITEM_NONE, IFACE_("Forward Axis"), ICON_NONE);
col.prop(ptr, "up_axis", UI_ITEM_NONE, IFACE_("Up Axis"), ICON_NONE);
}
/* Geometry options. */
if (ui::Layout *panel = layout.panel(C, "OBJ_export_geometry", false, IFACE_("Geometry"))) {
ui::Layout &col = panel->column(false);
col.prop(ptr, "export_uv", UI_ITEM_NONE, IFACE_("UV Coordinates"), ICON_NONE);
col.prop(ptr, "export_normals", UI_ITEM_NONE, IFACE_("Normals"), ICON_NONE);
col.prop(ptr, "export_colors", UI_ITEM_NONE, IFACE_("Colors"), ICON_NONE);
col.prop(ptr, "export_curves_as_nurbs", UI_ITEM_NONE, IFACE_("Curves as NURBS"), ICON_NONE);
col.prop(
ptr, "export_triangulated_mesh", UI_ITEM_NONE, IFACE_("Triangulated Mesh"), ICON_NONE);
col.prop(ptr, "apply_modifiers", UI_ITEM_NONE, IFACE_("Apply Modifiers"), ICON_NONE);
col.prop(ptr, "apply_transform", UI_ITEM_NONE, IFACE_("Apply Transform"), ICON_NONE);
col.prop(ptr, "export_eval_mode", UI_ITEM_NONE, IFACE_("Properties"), ICON_NONE);
}
/* Grouping options. */
if (ui::Layout *panel = layout.panel(C, "OBJ_export_grouping", false, IFACE_("Grouping"))) {
ui::Layout &col = panel->column(false);
col.prop(ptr, "export_object_groups", UI_ITEM_NONE, IFACE_("Object Groups"), ICON_NONE);
col.prop(ptr, "export_material_groups", UI_ITEM_NONE, IFACE_("Material Groups"), ICON_NONE);
col.prop(ptr, "export_vertex_groups", UI_ITEM_NONE, IFACE_("Vertex Groups"), ICON_NONE);
col.prop(ptr, "export_smooth_groups", UI_ITEM_NONE, IFACE_("Smooth Groups"), ICON_NONE);
ui::Layout &sub = col.column(false);
sub.enabled_set(export_smooth_groups);
sub.prop(
ptr, "smooth_group_bitflags", UI_ITEM_NONE, IFACE_("Smooth Group Bitflags"), ICON_NONE);
}
/* Material options. */
ui::PanelLayout panel = layout.panel(C, "OBJ_export_materials", false);
panel.header->use_property_split_set(false);
panel.header->prop(ptr, "export_materials", UI_ITEM_NONE, "", ICON_NONE);
panel.header->label(IFACE_("Materials"), ICON_NONE);
if (panel.body) {
ui::Layout &col = panel.body->column(false);
col.enabled_set(export_materials);
col.prop(ptr, "export_pbr_extensions", UI_ITEM_NONE, IFACE_("PBR Extensions"), ICON_NONE);
col.prop(ptr, "path_mode", UI_ITEM_NONE, IFACE_("Path Mode"), ICON_NONE);
}
/* Animation options. */
panel = layout.panel(C, "OBJ_export_animation", true);
panel.header->use_property_split_set(false);
panel.header->prop(ptr, "export_animation", UI_ITEM_NONE, "", ICON_NONE);
panel.header->label(IFACE_("Animation"), ICON_NONE);
if (panel.body) {
ui::Layout &col = panel.body->column(false);
col.enabled_set(export_animation);
col.prop(ptr, "start_frame", UI_ITEM_NONE, IFACE_("Frame Start"), ICON_NONE);
col.prop(ptr, "end_frame", UI_ITEM_NONE, IFACE_("End"), ICON_NONE);
}
}
static void wm_obj_export_draw(bContext *C, wmOperator *op)
{
ui_obj_export_settings(C, *op->layout, op->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 = scene->r.sfra;
changed = true;
}
if (end == INT_MAX) {
end = scene->r.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);
}
return changed;
}
void WM_OT_obj_export(wmOperatorType *ot)
{
PropertyRNA *prop;
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;
ot->flag = OPTYPE_PRESET;
WM_operator_properties_filesel(ot,
FILE_TYPE_FOLDER,
FILE_BLENDER,
FILE_SAVE,
WM_FILESEL_FILEPATH | WM_FILESEL_SHOW_PROPS,
FILE_DEFAULTDISPLAY,
FILE_SORT_DEFAULT);
/* 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 scene->r.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 scene->r.efra. */
INT_MIN,
INT_MAX,
"End Frame",
"The last frame to be exported",
INT_MIN,
INT_MAX);
/* Object transform options. */
prop = RNA_def_enum(
ot->srna, "forward_axis", io_transform_axis, IO_AXIS_NEGATIVE_Z, "Forward Axis", "");
RNA_def_property_update_runtime(prop, io_ui_forward_axis_update);
prop = RNA_def_enum(ot->srna, "up_axis", io_transform_axis, IO_AXIS_Y, "Up Axis", "");
RNA_def_property_update_runtime(prop, io_ui_up_axis_update);
RNA_def_float(
ot->srna,
"global_scale",
1.0f,
0.0001f,
10000.0f,
"Scale",
"Value by which to enlarge or shrink the objects with respect to the world's origin",
0.0001f,
10000.0f);
/* File Writer options. */
RNA_def_boolean(
ot->srna, "apply_modifiers", true, "Apply Modifiers", "Apply modifiers to exported meshes");
RNA_def_boolean(ot->srna,
"apply_transform",
true,
"Apply Transform",
"Apply object transforms to exported vertices");
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-corner "
"normals if smooth-shaded");
RNA_def_boolean(ot->srna, "export_colors", false, "Export Colors", "Export per-vertex colors");
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_pbr_extensions",
false,
"Export Materials with PBR Extensions",
"Export MTL library using PBR extensions (roughness, metallic, sheen, "
"coat, anisotropy, transmission)");
prop = RNA_def_enum(ot->srna,
"path_mode",
io_obj_path_mode,
PATH_REFERENCE_AUTO,
"Path Mode",
"Method used to reference paths");
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_EDITOR_FILEBROWSER);
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",
"Generate an OBJ group for each part of a geometry using a different material");
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",
"Generate smooth groups identifiers for each group of smooth faces, as "
"unique integer values by default");
RNA_def_boolean(
ot->srna,
"smooth_group_bitflags",
false,
"Bitflags Smooth Groups",
"If exporting smoothgroups, generate 'bitflags' values for the groups, instead of "
"unique integer values. The same bitflag value can be re-used for different groups of "
"smooth faces, as long as they have no common sharp edges or vertices");
/* Only show `.obj` or `.mtl` files by default. */
prop = RNA_def_string(ot->srna, "filter_glob", "*.obj;*.mtl", 0, "Extension Filter", "");
RNA_def_property_flag(prop, PROP_HIDDEN);
prop = RNA_def_string(ot->srna, "collection", nullptr, MAX_ID_NAME - 2, "Collection", nullptr);
RNA_def_property_flag(prop, PROP_HIDDEN);
}
static wmOperatorStatus wm_obj_import_exec(bContext *C, wmOperator *op)
{
OBJImportParams import_params;
import_params.global_scale = RNA_float_get(op->ptr, "global_scale");
import_params.clamp_size = RNA_float_get(op->ptr, "clamp_size");
import_params.forward_axis = eIOAxis(RNA_enum_get(op->ptr, "forward_axis"));
import_params.up_axis = eIOAxis(RNA_enum_get(op->ptr, "up_axis"));
import_params.use_split_objects = RNA_boolean_get(op->ptr, "use_split_objects");
import_params.use_split_groups = RNA_boolean_get(op->ptr, "use_split_groups");
import_params.import_vertex_groups = RNA_boolean_get(op->ptr, "import_vertex_groups");
import_params.validate_meshes = RNA_boolean_get(op->ptr, "validate_meshes");
import_params.close_spline_loops = RNA_boolean_get(op->ptr, "close_spline_loops");
char separator[2] = {};
RNA_string_get(op->ptr, "collection_separator", separator);
import_params.collection_separator = separator[0];
import_params.relative_paths = ((U.flag & USER_RELPATHS) != 0);
import_params.clear_selection = true;
import_params.mtl_name_collision_mode = eOBJMtlNameCollisionMode(
RNA_enum_get(op->ptr, "mtl_name_collision_mode"));
import_params.reports = op->reports;
const auto paths = ed::io::paths_from_operator_properties(op->ptr);
if (paths.is_empty()) {
BKE_report(op->reports, RPT_ERROR, "No filepath given");
return OPERATOR_CANCELLED;
}
for (const auto &path : paths) {
STRNCPY(import_params.filepath, path.c_str());
OBJ_import(C, &import_params);
/* Only first import clears selection. */
import_params.clear_selection = false;
};
Scene *scene = CTX_data_scene(C);
WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene);
WM_event_add_notifier(C, NC_SCENE | ND_OB_ACTIVE, scene);
WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene);
ED_outliner_select_sync_from_object_tag(C);
return OPERATOR_FINISHED;
}
static void ui_obj_import_settings(const bContext *C, ui::Layout &layout, PointerRNA *ptr)
{
layout.use_property_split_set(true);
layout.use_property_decorate_set(false);
if (ui::Layout *panel = layout.panel(C, "OBJ_import_general", false, IFACE_("General"))) {
ui::Layout &col = panel->column(false);
col.prop(ptr, "global_scale", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col.prop(ptr, "clamp_size", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col.prop(ptr, "forward_axis", UI_ITEM_NONE, IFACE_("Forward Axis"), ICON_NONE);
col.prop(ptr, "up_axis", UI_ITEM_NONE, IFACE_("Up Axis"), ICON_NONE);
}
if (ui::Layout *panel = layout.panel(C, "OBJ_import_options", false, IFACE_("Options"))) {
ui::Layout &col = panel->column(false);
col.prop(ptr, "use_split_objects", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col.prop(ptr, "use_split_groups", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col.prop(ptr, "import_vertex_groups", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col.prop(ptr, "validate_meshes", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col.prop(ptr, "close_spline_loops", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col.prop(ptr, "collection_separator", UI_ITEM_NONE, std::nullopt, ICON_NONE);
}
if (ui::Layout *panel = layout.panel(C, "OBJ_import_materials", false, IFACE_("Materials"))) {
ui::Layout &col = panel->column(false);
col.prop(ptr, "mtl_name_collision_mode", UI_ITEM_NONE, IFACE_("Name Collision"), ICON_NONE);
}
}
static void wm_obj_import_draw(bContext *C, wmOperator *op)
{
ui_obj_import_settings(C, *op->layout, op->ptr);
}
void WM_OT_obj_import(wmOperatorType *ot)
{
PropertyRNA *prop;
ot->name = "Import Wavefront OBJ";
ot->description = "Load a Wavefront OBJ scene";
ot->idname = "WM_OT_obj_import";
ot->flag = OPTYPE_UNDO | OPTYPE_PRESET;
ot->invoke = ed::io::filesel_drop_import_invoke;
ot->exec = wm_obj_import_exec;
ot->poll = WM_operator_winactive;
ot->ui = wm_obj_import_draw;
WM_operator_properties_filesel(ot,
FILE_TYPE_FOLDER,
FILE_BLENDER,
FILE_OPENFILE,
WM_FILESEL_FILEPATH | WM_FILESEL_SHOW_PROPS |
WM_FILESEL_DIRECTORY | WM_FILESEL_FILES,
FILE_DEFAULTDISPLAY,
FILE_SORT_DEFAULT);
RNA_def_float(
ot->srna,
"global_scale",
1.0f,
0.0001f,
10000.0f,
"Scale",
"Value by which to enlarge or shrink the objects with respect to the world's origin",
0.0001f,
10000.0f);
RNA_def_float(
ot->srna,
"clamp_size",
0.0f,
0.0f,
1000.0f,
"Clamp Bounding Box",
"Resize the objects to keep bounding box under this value. Value 0 disables clamping",
0.0f,
1000.0f);
prop = RNA_def_enum(
ot->srna, "forward_axis", io_transform_axis, IO_AXIS_NEGATIVE_Z, "Forward Axis", "");
RNA_def_property_update_runtime(prop, io_ui_forward_axis_update);
prop = RNA_def_enum(ot->srna, "up_axis", io_transform_axis, IO_AXIS_Y, "Up Axis", "");
RNA_def_property_update_runtime(prop, io_ui_up_axis_update);
RNA_def_boolean(ot->srna,
"use_split_objects",
true,
"Split By Object",
"Import each OBJ 'o' as a separate object");
RNA_def_boolean(ot->srna,
"use_split_groups",
false,
"Split By Group",
"Import each OBJ 'g' as a separate object");
RNA_def_boolean(ot->srna,
"import_vertex_groups",
false,
"Vertex Groups",
"Import OBJ groups as vertex groups");
RNA_def_boolean(
ot->srna,
"validate_meshes",
true,
"Validate Meshes",
"Ensure the data is valid "
"(when disabled, data may be imported which causes crashes displaying or editing)");
RNA_def_boolean(ot->srna,
"close_spline_loops",
true,
"Detect Cyclic Curves",
"Join curve endpoints if overlapping control points are detected "
"(if disabled, no curves will be cyclic)");
RNA_def_string(ot->srna,
"collection_separator",
nullptr,
2,
"Path Separator",
"Character used to separate objects name into hierarchical structure");
/* Material options */
RNA_def_enum(ot->srna,
"mtl_name_collision_mode",
io_obj_mtl_name_collision_mode,
OBJ_MTL_NAME_COLLISION_MAKE_UNIQUE,
"Material Name Collision",
"How to handle naming collisions when importing materials");
/* Only show `.obj` or `.mtl` files by default. */
prop = RNA_def_string(ot->srna, "filter_glob", "*.obj;*.mtl", 0, "Extension Filter", "");
RNA_def_property_flag(prop, PROP_HIDDEN);
}
namespace ed::io {
void obj_file_handler_add()
{
auto fh = std::make_unique<bke::FileHandlerType>();
STRNCPY_UTF8(fh->idname, "IO_FH_obj");
STRNCPY_UTF8(fh->import_operator, "WM_OT_obj_import");
STRNCPY_UTF8(fh->export_operator, "WM_OT_obj_export");
STRNCPY_UTF8(fh->label, "Wavefront OBJ");
STRNCPY_UTF8(fh->file_extensions_str, ".obj");
fh->poll_drop = poll_file_object_drop;
bke::file_handler_add(std::move(fh));
}
} // namespace ed::io
} // namespace blender
#endif /* WITH_IO_WAVEFRONT_OBJ */

View File

@@ -0,0 +1,22 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup editor/io
*/
#pragma once
namespace blender {
struct wmOperatorType;
void WM_OT_obj_export(wmOperatorType *ot);
void WM_OT_obj_import(wmOperatorType *ot);
namespace ed::io {
void obj_file_handler_add();
}
} // namespace blender

View File

@@ -0,0 +1,87 @@
/* SPDX-FileCopyrightText: 2008 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "io_ops.hh" /* own include */
#include "WM_api.hh"
#ifdef WITH_ALEMBIC
# include "io_alembic.hh"
#endif
#ifdef WITH_USD
# include "io_usd.hh"
#endif
#ifdef WITH_IO_FBX
# include "io_fbx_ops.hh"
#endif
#include "io_cache.hh"
#include "io_drop_import_file.hh"
#include "io_grease_pencil.hh"
#include "io_obj.hh"
#include "io_ply_ops.hh"
#include "io_stl_ops.hh"
namespace blender {
void ED_operatortypes_io()
{
#ifdef WITH_ALEMBIC
WM_operatortype_append(WM_OT_alembic_import);
WM_operatortype_append(WM_OT_alembic_export);
ed::io::alembic_file_handler_add();
#endif
#ifdef WITH_USD
WM_operatortype_append(WM_OT_usd_import);
WM_operatortype_append(WM_OT_usd_export);
ed::io::usd_file_handler_add();
#endif
#ifdef WITH_IO_GREASE_PENCIL
WM_operatortype_append(WM_OT_grease_pencil_import_svg);
ed::io::grease_pencil_file_handler_add();
# ifdef WITH_PUGIXML
WM_operatortype_append(WM_OT_grease_pencil_export_svg);
# endif
# ifdef WITH_HARU
WM_operatortype_append(WM_OT_grease_pencil_export_pdf);
# endif
#endif
WM_operatortype_append(CACHEFILE_OT_open);
WM_operatortype_append(CACHEFILE_OT_reload);
WM_operatortype_append(CACHEFILE_OT_layer_add);
WM_operatortype_append(CACHEFILE_OT_layer_remove);
WM_operatortype_append(CACHEFILE_OT_layer_move);
#ifdef WITH_IO_WAVEFRONT_OBJ
WM_operatortype_append(WM_OT_obj_export);
WM_operatortype_append(WM_OT_obj_import);
ed::io::obj_file_handler_add();
#endif
#ifdef WITH_IO_PLY
WM_operatortype_append(WM_OT_ply_export);
WM_operatortype_append(WM_OT_ply_import);
ed::io::ply_file_handler_add();
#endif
#ifdef WITH_IO_STL
WM_operatortype_append(WM_OT_stl_import);
WM_operatortype_append(WM_OT_stl_export);
ed::io::stl_file_handler_add();
#endif
#ifdef WITH_IO_FBX
WM_operatortype_append(WM_OT_fbx_import);
ed::io::fbx_file_handler_add();
#endif
WM_operatortype_append(WM_OT_drop_import_file);
ED_dropbox_drop_import_file();
}
} // namespace blender

View File

@@ -0,0 +1,15 @@
/* SPDX-FileCopyrightText: 2007 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup editor/io
*/
#pragma once
namespace blender {
void ED_operatortypes_io();
} // namespace blender

View File

@@ -0,0 +1,374 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup editor/io
*/
#ifdef WITH_IO_PLY
# include "BKE_context.hh"
# include "BKE_file_handler.hh"
# include "BKE_main.hh"
# include "BKE_report.hh"
# include "BLI_string.h"
# include "BLI_string_utf8.h"
# include "WM_api.hh"
# include "WM_types.hh"
# include "DNA_space_types.h"
# include "ED_fileselect.hh"
# include "ED_outliner.hh"
# include "RNA_access.hh"
# include "RNA_define.hh"
# include "BLT_translation.hh"
# include "UI_interface.hh"
# include "UI_interface_layout.hh"
# include "UI_resources.hh"
# include "IO_orientation.hh"
# include "IO_ply.hh"
# include "io_ply_ops.hh"
# include "io_utils.hh"
namespace blender {
static const EnumPropertyItem ply_vertex_colors_mode[] = {
{int(ePLYVertexColorMode::None), "NONE", 0, "None", "Do not import/export color attributes"},
{int(ePLYVertexColorMode::sRGB),
"SRGB",
0,
"sRGB",
"Vertex colors in the file are in sRGB color space"},
{int(ePLYVertexColorMode::Linear),
"LINEAR",
0,
"Linear",
"Vertex colors in the file are in linear color space"},
{0, nullptr, 0, nullptr, nullptr}};
static wmOperatorStatus wm_ply_export_invoke(bContext *C,
wmOperator *op,
const wmEvent * /*event*/)
{
ED_fileselect_ensure_default_filepath(C, op, ".ply");
WM_event_add_fileselect(C, op);
return OPERATOR_RUNNING_MODAL;
}
static wmOperatorStatus wm_ply_export_exec(bContext *C, wmOperator *op)
{
if (!RNA_struct_property_is_set_ex(op->ptr, "filepath", false)) {
BKE_report(op->reports, RPT_ERROR, "No filepath given");
return OPERATOR_CANCELLED;
}
PLYExportParams export_params;
export_params.file_base_for_tests[0] = '\0';
RNA_string_get(op->ptr, "filepath", export_params.filepath);
export_params.blen_filepath = CTX_data_main(C)->filepath;
export_params.forward_axis = eIOAxis(RNA_enum_get(op->ptr, "forward_axis"));
export_params.up_axis = eIOAxis(RNA_enum_get(op->ptr, "up_axis"));
export_params.global_scale = RNA_float_get(op->ptr, "global_scale");
export_params.apply_modifiers = RNA_boolean_get(op->ptr, "apply_modifiers");
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.vertex_colors = ePLYVertexColorMode(RNA_enum_get(op->ptr, "export_colors"));
export_params.export_attributes = RNA_boolean_get(op->ptr, "export_attributes");
export_params.export_triangulated_mesh = RNA_boolean_get(op->ptr, "export_triangulated_mesh");
export_params.ascii_format = RNA_boolean_get(op->ptr, "ascii_format");
RNA_string_get(op->ptr, "collection", export_params.collection);
export_params.reports = op->reports;
PLY_export(C, export_params);
if (BKE_reports_contain(op->reports, RPT_ERROR)) {
return OPERATOR_CANCELLED;
}
BKE_report(op->reports, RPT_INFO, "File exported successfully");
return OPERATOR_FINISHED;
}
static void wm_ply_export_draw(bContext *C, wmOperator *op)
{
ui::Layout &layout = *op->layout;
PointerRNA *ptr = op->ptr;
layout.use_property_split_set(true);
layout.use_property_decorate_set(false);
if (ui::Layout *panel = layout.panel(C, "PLY_export_general", false, IFACE_("General"))) {
ui::Layout &col = panel->column(false);
{
ui::Layout &sub = col.column(false, IFACE_("Format"));
sub.prop(ptr, "ascii_format", UI_ITEM_NONE, IFACE_("ASCII"), ICON_NONE);
}
/* The Selection only options only make sense when using regular export. */
if (CTX_wm_space_file(C)) {
ui::Layout &sub = col.column(false, IFACE_("Include"));
sub.prop(ptr, "export_selected_objects", UI_ITEM_NONE, IFACE_("Selection Only"), ICON_NONE);
}
col.prop(ptr, "global_scale", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col.prop(ptr, "forward_axis", UI_ITEM_NONE, IFACE_("Forward Axis"), ICON_NONE);
col.prop(ptr, "up_axis", UI_ITEM_NONE, IFACE_("Up Axis"), ICON_NONE);
}
if (ui::Layout *panel = layout.panel(C, "PLY_export_geometry", false, IFACE_("Geometry"))) {
ui::Layout &col = panel->column(false);
col.prop(ptr, "export_uv", UI_ITEM_NONE, IFACE_("UV Coordinates"), ICON_NONE);
col.prop(ptr, "export_normals", UI_ITEM_NONE, IFACE_("Vertex Normals"), ICON_NONE);
col.prop(ptr, "export_attributes", UI_ITEM_NONE, IFACE_("Vertex Attributes"), ICON_NONE);
col.prop(ptr, "export_colors", UI_ITEM_NONE, IFACE_("Vertex Colors"), ICON_NONE);
col.prop(
ptr, "export_triangulated_mesh", UI_ITEM_NONE, IFACE_("Triangulated Mesh"), ICON_NONE);
col.prop(ptr, "apply_modifiers", UI_ITEM_NONE, IFACE_("Apply Modifiers"), ICON_NONE);
}
}
/**
* Return true if any property in the UI is changed.
*/
static bool wm_ply_export_check(bContext * /*C*/, wmOperator *op)
{
char filepath[FILE_MAX];
bool changed = false;
RNA_string_get(op->ptr, "filepath", filepath);
if (!BLI_path_extension_check(filepath, ".ply")) {
BLI_path_extension_ensure(filepath, FILE_MAX, ".ply");
RNA_string_set(op->ptr, "filepath", filepath);
changed = true;
}
return changed;
}
void WM_OT_ply_export(wmOperatorType *ot)
{
PropertyRNA *prop;
ot->name = "Export PLY";
ot->description = "Save the scene to a PLY file";
ot->idname = "WM_OT_ply_export";
ot->invoke = wm_ply_export_invoke;
ot->exec = wm_ply_export_exec;
ot->poll = WM_operator_winactive;
ot->ui = wm_ply_export_draw;
ot->check = wm_ply_export_check;
ot->flag = OPTYPE_PRESET;
WM_operator_properties_filesel(ot,
FILE_TYPE_FOLDER,
FILE_BLENDER,
FILE_SAVE,
WM_FILESEL_FILEPATH | WM_FILESEL_SHOW_PROPS,
FILE_DEFAULTDISPLAY,
FILE_SORT_DEFAULT);
/* Object transform options. */
prop = RNA_def_enum(ot->srna, "forward_axis", io_transform_axis, IO_AXIS_Y, "Forward Axis", "");
RNA_def_property_update_runtime(prop, io_ui_forward_axis_update);
prop = RNA_def_enum(ot->srna, "up_axis", io_transform_axis, IO_AXIS_Z, "Up Axis", "");
RNA_def_property_update_runtime(prop, io_ui_up_axis_update);
RNA_def_float(
ot->srna,
"global_scale",
1.0f,
0.0001f,
10000.0f,
"Scale",
"Value by which to enlarge or shrink the objects with respect to the world's origin",
0.0001f,
10000.0f);
/* File Writer options. */
RNA_def_boolean(
ot->srna, "apply_modifiers", true, "Apply Modifiers", "Apply modifiers to exported meshes");
RNA_def_boolean(ot->srna,
"export_selected_objects",
false,
"Export Selected Objects",
"Export only selected objects instead of all supported objects");
prop = RNA_def_string(ot->srna,
"collection",
nullptr,
MAX_ID_NAME - 2,
"Source Collection",
"Export only objects from this collection (and its children)");
RNA_def_property_flag(prop, PROP_HIDDEN);
RNA_def_boolean(ot->srna, "export_uv", true, "Export UVs", "");
RNA_def_boolean(
ot->srna,
"export_normals",
false,
"Export Vertex Normals",
"Export specific vertex normals if available, export calculated normals otherwise");
RNA_def_enum(ot->srna,
"export_colors",
ply_vertex_colors_mode,
int(ePLYVertexColorMode::sRGB),
"Export Vertex Colors",
"Export vertex color attributes");
RNA_def_boolean(ot->srna,
"export_attributes",
true,
"Export Vertex Attributes",
"Export custom vertex attributes");
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,
"ascii_format",
false,
"ASCII Format",
"Export file in ASCII format, export as binary otherwise");
/* Only show `.ply` files by default. */
prop = RNA_def_string(ot->srna, "filter_glob", "*.ply", 0, "Extension Filter", "");
RNA_def_property_flag(prop, PROP_HIDDEN);
}
static wmOperatorStatus wm_ply_import_exec(bContext *C, wmOperator *op)
{
PLYImportParams params;
params.forward_axis = eIOAxis(RNA_enum_get(op->ptr, "forward_axis"));
params.up_axis = eIOAxis(RNA_enum_get(op->ptr, "up_axis"));
params.use_scene_unit = RNA_boolean_get(op->ptr, "use_scene_unit");
params.global_scale = RNA_float_get(op->ptr, "global_scale");
params.merge_verts = RNA_boolean_get(op->ptr, "merge_verts");
params.import_attributes = RNA_boolean_get(op->ptr, "import_attributes");
params.vertex_colors = ePLYVertexColorMode(RNA_enum_get(op->ptr, "import_colors"));
params.reports = op->reports;
const auto paths = ed::io::paths_from_operator_properties(op->ptr);
if (paths.is_empty()) {
BKE_report(op->reports, RPT_ERROR, "No filepath given");
return OPERATOR_CANCELLED;
}
for (const auto &path : paths) {
STRNCPY(params.filepath, path.c_str());
PLY_import(C, params);
};
Scene *scene = CTX_data_scene(C);
WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene);
WM_event_add_notifier(C, NC_SCENE | ND_OB_ACTIVE, scene);
WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene);
ED_outliner_select_sync_from_object_tag(C);
return OPERATOR_FINISHED;
}
static void ui_ply_import_settings(const bContext *C, ui::Layout &layout, PointerRNA *ptr)
{
layout.use_property_split_set(true);
layout.use_property_decorate_set(false);
if (ui::Layout *panel = layout.panel(C, "PLY_import_general", false, IFACE_("General"))) {
ui::Layout &col = panel->column(false);
col.prop(ptr, "global_scale", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col.prop(ptr, "use_scene_unit", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col.prop(ptr, "forward_axis", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col.prop(ptr, "up_axis", UI_ITEM_NONE, std::nullopt, ICON_NONE);
}
if (ui::Layout *panel = layout.panel(C, "PLY_import_options", false, IFACE_("Options"))) {
ui::Layout &col = panel->column(false);
col.prop(ptr, "merge_verts", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col.prop(ptr, "import_colors", UI_ITEM_NONE, std::nullopt, ICON_NONE);
}
}
static void wm_ply_import_draw(bContext *C, wmOperator *op)
{
ui_ply_import_settings(C, *op->layout, op->ptr);
}
void WM_OT_ply_import(wmOperatorType *ot)
{
PropertyRNA *prop;
ot->name = "Import PLY";
ot->description = "Import an PLY file as an object";
ot->idname = "WM_OT_ply_import";
ot->invoke = ed::io::filesel_drop_import_invoke;
ot->exec = wm_ply_import_exec;
ot->ui = wm_ply_import_draw;
ot->poll = WM_operator_winactive;
ot->flag = OPTYPE_UNDO | OPTYPE_PRESET;
WM_operator_properties_filesel(ot,
FILE_TYPE_FOLDER,
FILE_BLENDER,
FILE_OPENFILE,
WM_FILESEL_FILEPATH | WM_FILESEL_FILES | WM_FILESEL_DIRECTORY |
WM_FILESEL_SHOW_PROPS,
FILE_DEFAULTDISPLAY,
FILE_SORT_DEFAULT);
RNA_def_float(ot->srna, "global_scale", 1.0f, 1e-6f, 1e6f, "Scale", "", 0.001f, 1000.0f);
RNA_def_boolean(ot->srna,
"use_scene_unit",
false,
"Scene Unit",
"Apply current scene's unit (as defined by unit scale) to imported data");
prop = RNA_def_enum(ot->srna, "forward_axis", io_transform_axis, IO_AXIS_Y, "Forward Axis", "");
RNA_def_property_update_runtime(prop, io_ui_forward_axis_update);
prop = RNA_def_enum(ot->srna, "up_axis", io_transform_axis, IO_AXIS_Z, "Up Axis", "");
RNA_def_property_update_runtime(prop, io_ui_up_axis_update);
RNA_def_boolean(ot->srna, "merge_verts", false, "Merge Vertices", "Merges vertices by distance");
RNA_def_enum(ot->srna,
"import_colors",
ply_vertex_colors_mode,
int(ePLYVertexColorMode::sRGB),
"Vertex Colors",
"Import vertex color attributes");
RNA_def_boolean(
ot->srna, "import_attributes", true, "Vertex Attributes", "Import custom vertex attributes");
/* Only show `.ply` files by default. */
prop = RNA_def_string(ot->srna, "filter_glob", "*.ply", 0, "Extension Filter", "");
RNA_def_property_flag(prop, PROP_HIDDEN);
}
namespace ed::io {
void ply_file_handler_add()
{
auto fh = std::make_unique<bke::FileHandlerType>();
STRNCPY_UTF8(fh->idname, "IO_FH_ply");
STRNCPY_UTF8(fh->import_operator, "WM_OT_ply_import");
STRNCPY_UTF8(fh->export_operator, "WM_OT_ply_export");
STRNCPY_UTF8(fh->label, "Stanford PLY");
STRNCPY_UTF8(fh->file_extensions_str, ".ply");
fh->poll_drop = poll_file_object_drop;
bke::file_handler_add(std::move(fh));
}
} // namespace ed::io
} // namespace blender
#endif /* WITH_IO_PLY */

View File

@@ -0,0 +1,22 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup editor/io
*/
#pragma once
namespace blender {
struct wmOperatorType;
void WM_OT_ply_export(wmOperatorType *ot);
void WM_OT_ply_import(wmOperatorType *ot);
namespace ed::io {
void ply_file_handler_add();
}
} // namespace blender

View File

@@ -0,0 +1,358 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup editor/io
*/
#ifdef WITH_IO_STL
# include "BKE_context.hh"
# include "BKE_file_handler.hh"
# include "BKE_report.hh"
# include "BLI_string.h"
# include "BLI_string_utf8.h"
# include "WM_api.hh"
# include "WM_types.hh"
# include "DNA_space_types.h"
# include "ED_fileselect.hh"
# include "ED_outliner.hh"
# include "RNA_access.hh"
# include "RNA_define.hh"
# include "BLT_translation.hh"
# include "UI_interface.hh"
# include "UI_interface_layout.hh"
# include "UI_resources.hh"
# include "IO_stl.hh"
# include "io_stl_ops.hh"
# include "io_utils.hh"
namespace blender {
static const EnumPropertyItem io_stl_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",
"Export objects as they appear in the viewport (Multiresolution modifiers in Sculpt Mode "
"will not be evaluated)"},
{0, nullptr, 0, nullptr, nullptr}};
static wmOperatorStatus wm_stl_export_invoke(bContext *C,
wmOperator *op,
const wmEvent * /*event*/)
{
ED_fileselect_ensure_default_filepath(C, op, ".stl");
WM_event_add_fileselect(C, op);
return OPERATOR_RUNNING_MODAL;
}
static wmOperatorStatus wm_stl_export_exec(bContext *C, wmOperator *op)
{
if (!RNA_struct_property_is_set_ex(op->ptr, "filepath", false)) {
BKE_report(op->reports, RPT_ERROR, "No filename given");
return OPERATOR_CANCELLED;
}
STLExportParams export_params;
RNA_string_get(op->ptr, "filepath", export_params.filepath);
export_params.forward_axis = eIOAxis(RNA_enum_get(op->ptr, "forward_axis"));
export_params.up_axis = eIOAxis(RNA_enum_get(op->ptr, "up_axis"));
export_params.global_scale = RNA_float_get(op->ptr, "global_scale");
export_params.apply_modifiers = RNA_boolean_get(op->ptr, "apply_modifiers");
export_params.evaluation_mode = eEvaluationMode(RNA_enum_get(op->ptr, "evaluation_mode"));
export_params.export_selected_objects = RNA_boolean_get(op->ptr, "export_selected_objects");
export_params.use_scene_unit = RNA_boolean_get(op->ptr, "use_scene_unit");
export_params.ascii_format = RNA_boolean_get(op->ptr, "ascii_format");
export_params.use_batch = RNA_boolean_get(op->ptr, "use_batch");
RNA_string_get(op->ptr, "collection", export_params.collection);
export_params.reports = op->reports;
STL_export(C, &export_params);
if (BKE_reports_contain(op->reports, RPT_ERROR)) {
return OPERATOR_CANCELLED;
}
BKE_report(op->reports, RPT_INFO, "File exported successfully");
return OPERATOR_FINISHED;
}
static void wm_stl_export_draw(bContext *C, wmOperator *op)
{
ui::Layout &layout = *op->layout;
PointerRNA *ptr = op->ptr;
layout.use_property_split_set(true);
layout.use_property_decorate_set(false);
if (ui::Layout *panel = layout.panel(C, "STL_export_general", false, IFACE_("General"))) {
ui::Layout &col = panel->column(false);
ui::Layout *sub = &col.column(false, IFACE_("Format"));
sub->prop(ptr, "ascii_format", UI_ITEM_NONE, IFACE_("ASCII"), ICON_NONE);
/* The Batch mode and Selection only options only make sense when using regular export. */
if (CTX_wm_space_file(C)) {
col.prop(ptr, "use_batch", UI_ITEM_NONE, IFACE_("Batch"), ICON_NONE);
sub = &col.column(false, IFACE_("Include"));
sub->prop(ptr, "export_selected_objects", UI_ITEM_NONE, IFACE_("Selection Only"), ICON_NONE);
}
sub->prop(ptr, "global_scale", UI_ITEM_NONE, IFACE_("Scale"), ICON_NONE);
sub->prop(ptr, "use_scene_unit", UI_ITEM_NONE, IFACE_("Scene Unit"), ICON_NONE);
sub->prop(ptr, "forward_axis", UI_ITEM_NONE, IFACE_("Forward"), ICON_NONE);
sub->prop(ptr, "up_axis", UI_ITEM_NONE, IFACE_("Up"), ICON_NONE);
}
if (ui::Layout *panel = layout.panel(C, "STL_export_geometry", false, IFACE_("Geometry"))) {
ui::Layout &col = panel->column(false);
col.prop(ptr, "apply_modifiers", UI_ITEM_NONE, IFACE_("Apply Modifiers"), ICON_NONE);
col.prop(ptr, "evaluation_mode", UI_ITEM_NONE, IFACE_("Properties"), ICON_NONE);
}
}
/**
* Return true if any property in the UI is changed.
*/
static bool wm_stl_export_check(bContext * /*C*/, wmOperator *op)
{
char filepath[FILE_MAX];
bool changed = false;
bool use_batch = RNA_boolean_get(op->ptr, "use_batch");
RNA_string_get(op->ptr, "filepath", filepath);
/* Enforce an extension on the filepath unless Batch mode is used. Batch mode
* will perform substitutions, including the extension, during its processing. */
if (!use_batch && !BLI_path_extension_check(filepath, ".stl")) {
BLI_path_extension_ensure(filepath, FILE_MAX, ".stl");
RNA_string_set(op->ptr, "filepath", filepath);
changed = true;
}
return changed;
}
void WM_OT_stl_export(wmOperatorType *ot)
{
PropertyRNA *prop;
ot->name = "Export STL";
ot->description = "Save the scene to an STL file";
ot->idname = "WM_OT_stl_export";
ot->invoke = wm_stl_export_invoke;
ot->exec = wm_stl_export_exec;
ot->poll = WM_operator_winactive;
ot->ui = wm_stl_export_draw;
ot->check = wm_stl_export_check;
ot->flag = OPTYPE_PRESET;
WM_operator_properties_filesel(ot,
FILE_TYPE_FOLDER,
FILE_BLENDER,
FILE_SAVE,
WM_FILESEL_FILEPATH | WM_FILESEL_SHOW_PROPS,
FILE_DEFAULTDISPLAY,
FILE_SORT_DEFAULT);
RNA_def_boolean(ot->srna,
"ascii_format",
false,
"ASCII Format",
"Export file in ASCII format, export as binary otherwise");
RNA_def_boolean(
ot->srna, "use_batch", false, "Batch Export", "Export each object to a separate file");
RNA_def_boolean(ot->srna,
"export_selected_objects",
false,
"Export Selected Objects",
"Export only selected objects instead of all supported objects");
prop = RNA_def_string(ot->srna,
"collection",
nullptr,
MAX_ID_NAME - 2,
"Source Collection",
"Export only objects from this collection (and its children)");
RNA_def_property_flag(prop, PROP_HIDDEN);
RNA_def_float(ot->srna, "global_scale", 1.0f, 1e-6f, 1e6f, "Scale", "", 0.001f, 1000.0f);
RNA_def_boolean(ot->srna,
"use_scene_unit",
false,
"Scene Unit",
"Apply current scene's unit (as defined by unit scale) to exported data");
prop = RNA_def_enum(ot->srna, "forward_axis", io_transform_axis, IO_AXIS_Y, "Forward Axis", "");
RNA_def_property_update_runtime(prop, io_ui_forward_axis_update);
prop = RNA_def_enum(ot->srna, "up_axis", io_transform_axis, IO_AXIS_Z, "Up Axis", "");
RNA_def_property_update_runtime(prop, io_ui_up_axis_update);
RNA_def_boolean(
ot->srna, "apply_modifiers", true, "Apply Modifiers", "Apply modifiers to exported meshes");
RNA_def_enum(ot->srna,
"evaluation_mode",
io_stl_export_evaluation_mode,
DAG_EVAL_RENDER,
"Object Properties",
"Determines properties like object visibility, modifiers etc., where they differ "
"for Render and Viewport");
/* Only show `.stl` files by default. */
prop = RNA_def_string(ot->srna, "filter_glob", "*.stl", 0, "Extension Filter", "");
RNA_def_property_flag(prop, PROP_HIDDEN);
}
static wmOperatorStatus wm_stl_import_exec(bContext *C, wmOperator *op)
{
STLImportParams params;
params.forward_axis = eIOAxis(RNA_enum_get(op->ptr, "forward_axis"));
params.up_axis = eIOAxis(RNA_enum_get(op->ptr, "up_axis"));
params.use_facet_normal = RNA_boolean_get(op->ptr, "use_facet_normal");
params.use_scene_unit = RNA_boolean_get(op->ptr, "use_scene_unit");
params.global_scale = RNA_float_get(op->ptr, "global_scale");
params.use_mesh_validate = RNA_boolean_get(op->ptr, "use_mesh_validate");
params.reports = op->reports;
const auto paths = ed::io::paths_from_operator_properties(op->ptr);
if (paths.is_empty()) {
BKE_report(op->reports, RPT_ERROR, "No filepath given");
return OPERATOR_CANCELLED;
}
for (const auto &path : paths) {
STRNCPY(params.filepath, path.c_str());
STL_import(C, &params);
}
Scene *scene = CTX_data_scene(C);
WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene);
WM_event_add_notifier(C, NC_SCENE | ND_OB_ACTIVE, scene);
WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene);
ED_outliner_select_sync_from_object_tag(C);
return OPERATOR_FINISHED;
}
static bool wm_stl_import_check(bContext * /*C*/, wmOperator *op)
{
const int num_axes = 3;
/* Both forward and up axes cannot be the same (or same except opposite sign). */
if (RNA_enum_get(op->ptr, "forward_axis") % num_axes ==
(RNA_enum_get(op->ptr, "up_axis") % num_axes))
{
RNA_enum_set(op->ptr, "up_axis", RNA_enum_get(op->ptr, "up_axis") % num_axes + 1);
return true;
}
return false;
}
static void ui_stl_import_settings(const bContext *C, ui::Layout *layout, PointerRNA *ptr)
{
layout->use_property_split_set(true);
layout->use_property_decorate_set(false);
if (ui::Layout *panel = layout->panel(C, "STL_import_general", false, IFACE_("General"))) {
ui::Layout &col = panel->column(false);
col.prop(ptr, "global_scale", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col.prop(ptr, "use_scene_unit", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col.prop(ptr, "forward_axis", UI_ITEM_NONE, IFACE_("Forward Axis"), ICON_NONE);
col.prop(ptr, "up_axis", UI_ITEM_NONE, std::nullopt, ICON_NONE);
}
if (ui::Layout *panel = layout->panel(C, "STL_import_options", false, IFACE_("Options"))) {
ui::Layout &col = panel->column(false);
col.prop(ptr, "use_facet_normal", UI_ITEM_NONE, std::nullopt, ICON_NONE);
col.prop(ptr, "use_mesh_validate", UI_ITEM_NONE, std::nullopt, ICON_NONE);
}
}
static void wm_stl_import_draw(bContext *C, wmOperator *op)
{
ui_stl_import_settings(C, op->layout, op->ptr);
}
void WM_OT_stl_import(wmOperatorType *ot)
{
PropertyRNA *prop;
ot->name = "Import STL";
ot->description = "Import an STL file as an object";
ot->idname = "WM_OT_stl_import";
ot->invoke = ed::io::filesel_drop_import_invoke;
ot->exec = wm_stl_import_exec;
ot->poll = WM_operator_winactive;
ot->check = wm_stl_import_check;
ot->ui = wm_stl_import_draw;
ot->flag = OPTYPE_UNDO | OPTYPE_PRESET;
WM_operator_properties_filesel(ot,
FILE_TYPE_FOLDER,
FILE_BLENDER,
FILE_OPENFILE,
WM_FILESEL_FILEPATH | WM_FILESEL_FILES | WM_FILESEL_DIRECTORY |
WM_FILESEL_SHOW_PROPS,
FILE_DEFAULTDISPLAY,
FILE_SORT_DEFAULT);
RNA_def_float(ot->srna, "global_scale", 1.0f, 1e-6f, 1e6f, "Scale", "", 0.001f, 1000.0f);
RNA_def_boolean(ot->srna,
"use_scene_unit",
false,
"Scene Unit",
"Apply current scene's unit (as defined by unit scale) to imported data");
RNA_def_boolean(ot->srna,
"use_facet_normal",
false,
"Facet Normals",
"Use (import) facet normals (note that this will still give flat shading)");
RNA_def_enum(ot->srna, "forward_axis", io_transform_axis, IO_AXIS_Y, "Forward Axis", "");
RNA_def_enum(ot->srna, "up_axis", io_transform_axis, IO_AXIS_Z, "Up Axis", "");
RNA_def_boolean(
ot->srna,
"use_mesh_validate",
true,
"Validate Mesh",
"Ensure the data is valid "
"(when disabled, data may be imported which causes crashes displaying or editing)");
/* Only show `.stl` files by default. */
prop = RNA_def_string(ot->srna, "filter_glob", "*.stl", 0, "Extension Filter", "");
RNA_def_property_flag(prop, PROP_HIDDEN);
}
namespace ed::io {
void stl_file_handler_add()
{
auto fh = std::make_unique<bke::FileHandlerType>();
STRNCPY_UTF8(fh->idname, "IO_FH_stl");
STRNCPY_UTF8(fh->import_operator, "WM_OT_stl_import");
STRNCPY_UTF8(fh->export_operator, "WM_OT_stl_export");
STRNCPY_UTF8(fh->label, "STL");
STRNCPY_UTF8(fh->file_extensions_str, ".stl");
fh->poll_drop = poll_file_object_drop;
bke::file_handler_add(std::move(fh));
}
} // namespace ed::io
} // namespace blender
#endif /* WITH_IO_STL */

View File

@@ -0,0 +1,22 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup editor/io
*/
#pragma once
namespace blender {
struct wmOperatorType;
void WM_OT_stl_export(wmOperatorType *ot);
void WM_OT_stl_import(wmOperatorType *ot);
namespace ed::io {
void stl_file_handler_add();
}
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
/* SPDX-FileCopyrightText: 2019 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup editor/io
*/
namespace blender {
struct wmOperatorType;
void WM_OT_usd_export(wmOperatorType *ot);
void WM_OT_usd_import(wmOperatorType *ot);
namespace ed::io {
void usd_file_handler_add();
}
} // namespace blender

View File

@@ -0,0 +1,136 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <fmt/format.h>
#include "BLI_path_utils.hh"
#include "BLI_string_utf8.h"
#include "BLT_translation.hh"
#include "BKE_context.hh"
#include "BKE_main.hh"
#include "DNA_space_types.h"
#include "RNA_access.hh"
#include "RNA_prototypes.hh"
#include "WM_api.hh"
#include "io_utils.hh"
namespace blender::ed::io {
wmOperatorStatus filesel_drop_import_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
{
PropertyRNA *filepath_prop = RNA_struct_find_property(op->ptr, "filepath");
PropertyRNA *directory_prop = RNA_struct_find_property(op->ptr, "directory");
if ((filepath_prop && RNA_property_is_set(op->ptr, filepath_prop)) ||
(directory_prop && RNA_property_is_set(op->ptr, directory_prop)))
{
std::string title;
PropertyRNA *files_prop = RNA_struct_find_property(op->ptr, "files");
if (directory_prop && files_prop) {
const auto files = paths_from_operator_properties(op->ptr);
if (files.size() == 1) {
title = files[0];
}
else {
title = fmt::format(fmt::runtime(TIP_("Import {} files")), files.size());
}
}
else {
char filepath[FILE_MAX];
RNA_string_get(op->ptr, "filepath", filepath);
title = filepath;
}
return WM_operator_props_dialog_popup(
C, op, 350, std::move(title), WM_operatortype_name(op->type, op->ptr));
}
WM_event_add_fileselect(C, op);
return OPERATOR_RUNNING_MODAL;
}
bool poll_file_object_drop(const bContext *C, bke::FileHandlerType * /*fh*/)
{
View3D *v3d = CTX_wm_view3d(C);
SpaceOutliner *space_outliner = CTX_wm_space_outliner(C);
ARegion *region = CTX_wm_region(C);
if (!region || region->regiontype != RGN_TYPE_WINDOW) {
return false;
}
if (v3d) {
return true;
}
if (space_outliner && space_outliner->outlinevis == SO_VIEW_LAYER) {
return true;
}
return false;
}
Vector<std::string> paths_from_operator_properties(PointerRNA *ptr)
{
Vector<std::string> paths;
PropertyRNA *directory_prop = RNA_struct_find_property(ptr, "directory");
PropertyRNA *relative_path_prop = RNA_struct_find_property(ptr, "relative_path");
const bool is_relative_path = relative_path_prop ?
RNA_property_boolean_get(ptr, relative_path_prop) :
false;
if (RNA_property_is_set(ptr, directory_prop)) {
char directory[FILE_MAX], name[FILE_MAX];
RNA_string_get(ptr, "directory", directory);
if (is_relative_path && !BLI_path_is_rel(directory)) {
BLI_path_rel(directory, BKE_main_blendfile_path_from_global());
}
PropertyRNA *files_prop = RNA_struct_find_collection_property_check(
*ptr, "files", RNA_OperatorFileListElement);
BLI_assert(files_prop);
RNA_PROP_BEGIN (ptr, file_ptr, files_prop) {
RNA_string_get(&file_ptr, "name", name);
char path[FILE_MAX];
BLI_path_join(path, sizeof(path), directory, name);
BLI_path_normalize(path);
paths.append_non_duplicates(path);
}
RNA_PROP_END;
}
PropertyRNA *filepath_prop = RNA_struct_find_property(ptr, "filepath");
if (filepath_prop && RNA_property_is_set(ptr, filepath_prop)) {
char filepath[FILE_MAX];
RNA_string_get(ptr, "filepath", filepath);
if (is_relative_path && !BLI_path_is_rel(filepath)) {
BLI_path_rel(filepath, BKE_main_blendfile_path_from_global());
}
paths.append_non_duplicates(filepath);
}
return paths;
}
void paths_to_operator_properties(PointerRNA *ptr, const Span<std::string> paths)
{
char dir[FILE_MAX];
BLI_path_split_dir_part(paths[0].c_str(), dir, sizeof(dir));
RNA_string_set(ptr, "directory", dir);
RNA_collection_clear(ptr, "files");
for (const auto &path : paths) {
char file[FILE_MAX];
STRNCPY_UTF8(file, path.c_str());
BLI_path_rel(file, dir);
PointerRNA itemptr{};
RNA_collection_add(ptr, "files", &itemptr);
BLI_assert_msg(BLI_path_is_rel(file), "Expected path to be relative (start with '//')");
RNA_string_set(&itemptr, "name", file + 2);
}
}
} // namespace blender::ed::io

View File

@@ -0,0 +1,36 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "WM_types.hh"
#include "BLI_vector.hh"
namespace blender {
struct wmOperator;
namespace bke {
} // namespace bke
namespace ed::io {
/**
* Shows a import dialog if the operator was invoked with filepath properties set,
* otherwise invokes the file-select window.
*/
wmOperatorStatus filesel_drop_import_invoke(bContext *C, wmOperator *op, const wmEvent *event);
bool poll_file_object_drop(const bContext *C, bke::FileHandlerType *fh);
/**
* Return all paths stored in the pointer.
* Properties in pointer should include a `directory` #PropertySubType::PROP_FILEPATH property and
* a `files` #RNA_OperatorFileListElement collection property.
* If the pointer has a `filepath` property is also returned as fallback.
*/
Vector<std::string> paths_from_operator_properties(PointerRNA *ptr);
void paths_to_operator_properties(PointerRNA *ptr, const Span<std::string> paths);
} // namespace ed::io
} // namespace blender