Previous code would consider two different face groups sharing no common edges as fully isolated from each other, and could assign them the same bitflag facegroup value. Following FBX recent option to export these bitflags smoothgroups (!135646), also consider that two different face groups are connected if they only share common vertices, and assign them different bitflags values. NOTE: This seems to be the expected behavior in major DCCs actually using smoothgroups, only considering boundary edges create groups that generate broken shading when imported by these tools. NOTE: The 'unique integer identifers' option is kept for OBJ exporter, as such OBJ files are also found on internet, depending on which app generated them. Pull Request: https://projects.blender.org/blender/blender/pulls/135998
587 lines
22 KiB
C++
587 lines
22 KiB
C++
/* 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 "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_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"
|
|
|
|
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 int 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 int 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.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, uiLayout *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");
|
|
|
|
uiLayoutSetPropSep(layout, true);
|
|
uiLayoutSetPropDecorate(layout, false);
|
|
|
|
/* Object General options. */
|
|
if (uiLayout *panel = uiLayoutPanel(C, layout, "OBJ_export_general", false, IFACE_("General"))) {
|
|
uiLayout *col = uiLayoutColumn(panel, false);
|
|
|
|
if (CTX_wm_space_file(C)) {
|
|
uiLayout *sub = uiLayoutColumnWithHeading(col, false, IFACE_("Include"));
|
|
uiItemR(
|
|
sub, ptr, "export_selected_objects", UI_ITEM_NONE, IFACE_("Selection Only"), ICON_NONE);
|
|
}
|
|
|
|
uiItemR(col, ptr, "global_scale", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
|
uiItemR(col, ptr, "forward_axis", UI_ITEM_NONE, IFACE_("Forward Axis"), ICON_NONE);
|
|
uiItemR(col, ptr, "up_axis", UI_ITEM_NONE, IFACE_("Up Axis"), ICON_NONE);
|
|
}
|
|
|
|
/* Geometry options. */
|
|
if (uiLayout *panel = uiLayoutPanel(C, layout, "OBJ_export_geometry", false, IFACE_("Geometry")))
|
|
{
|
|
uiLayout *col = uiLayoutColumn(panel, false);
|
|
uiItemR(col, ptr, "export_uv", UI_ITEM_NONE, IFACE_("UV Coordinates"), ICON_NONE);
|
|
uiItemR(col, ptr, "export_normals", UI_ITEM_NONE, IFACE_("Normals"), ICON_NONE);
|
|
uiItemR(col, ptr, "export_colors", UI_ITEM_NONE, IFACE_("Colors"), ICON_NONE);
|
|
uiItemR(
|
|
col, ptr, "export_curves_as_nurbs", UI_ITEM_NONE, IFACE_("Curves as NURBS"), ICON_NONE);
|
|
|
|
uiItemR(col,
|
|
ptr,
|
|
"export_triangulated_mesh",
|
|
UI_ITEM_NONE,
|
|
IFACE_("Triangulated Mesh"),
|
|
ICON_NONE);
|
|
uiItemR(col, ptr, "apply_modifiers", UI_ITEM_NONE, IFACE_("Apply Modifiers"), ICON_NONE);
|
|
uiItemR(col, ptr, "export_eval_mode", UI_ITEM_NONE, IFACE_("Properties"), ICON_NONE);
|
|
}
|
|
|
|
/* Grouping options. */
|
|
if (uiLayout *panel = uiLayoutPanel(C, layout, "OBJ_export_grouping", false, IFACE_("Grouping")))
|
|
{
|
|
uiLayout *col = uiLayoutColumn(panel, false);
|
|
uiItemR(col, ptr, "export_object_groups", UI_ITEM_NONE, IFACE_("Object Groups"), ICON_NONE);
|
|
uiItemR(
|
|
col, ptr, "export_material_groups", UI_ITEM_NONE, IFACE_("Material Groups"), ICON_NONE);
|
|
uiItemR(col, ptr, "export_vertex_groups", UI_ITEM_NONE, IFACE_("Vertex Groups"), ICON_NONE);
|
|
uiItemR(col, ptr, "export_smooth_groups", UI_ITEM_NONE, IFACE_("Smooth Groups"), ICON_NONE);
|
|
col = uiLayoutColumn(col, false);
|
|
uiLayoutSetEnabled(col, export_smooth_groups);
|
|
uiItemR(col,
|
|
ptr,
|
|
"smooth_group_bitflags",
|
|
UI_ITEM_NONE,
|
|
IFACE_("Smooth Group Bitflags"),
|
|
ICON_NONE);
|
|
}
|
|
|
|
/* Material options. */
|
|
PanelLayout panel = uiLayoutPanel(C, layout, "OBJ_export_materials", false);
|
|
uiLayoutSetPropSep(panel.header, false);
|
|
uiItemR(panel.header, ptr, "export_materials", UI_ITEM_NONE, "", ICON_NONE);
|
|
uiItemL(panel.header, IFACE_("Materials"), ICON_NONE);
|
|
if (panel.body) {
|
|
uiLayout *col = uiLayoutColumn(panel.body, false);
|
|
uiLayoutSetEnabled(col, export_materials);
|
|
|
|
uiItemR(col, ptr, "export_pbr_extensions", UI_ITEM_NONE, IFACE_("PBR Extensions"), ICON_NONE);
|
|
uiItemR(col, ptr, "path_mode", UI_ITEM_NONE, IFACE_("Path Mode"), ICON_NONE);
|
|
}
|
|
|
|
/* Animation options. */
|
|
panel = uiLayoutPanel(C, layout, "OBJ_export_animation", true);
|
|
uiLayoutSetPropSep(panel.header, false);
|
|
uiItemR(panel.header, ptr, "export_animation", UI_ITEM_NONE, "", ICON_NONE);
|
|
uiItemL(panel.header, IFACE_("Animation"), ICON_NONE);
|
|
if (panel.body) {
|
|
uiLayout *col = uiLayoutColumn(panel.body, false);
|
|
uiLayoutSetEnabled(col, export_animation);
|
|
|
|
uiItemR(col, ptr, "start_frame", UI_ITEM_NONE, IFACE_("Frame Start"), ICON_NONE);
|
|
uiItemR(col, 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_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)");
|
|
RNA_def_enum(ot->srna,
|
|
"path_mode",
|
|
io_obj_path_mode,
|
|
PATH_REFERENCE_AUTO,
|
|
"Path Mode",
|
|
"Method used to reference paths");
|
|
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_IDPROP_NAME, "Collection", nullptr);
|
|
RNA_def_property_flag(prop, PROP_HIDDEN);
|
|
}
|
|
|
|
static int 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.reports = op->reports;
|
|
|
|
const auto paths = blender::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, uiLayout *layout, PointerRNA *ptr)
|
|
{
|
|
uiLayoutSetPropSep(layout, true);
|
|
uiLayoutSetPropDecorate(layout, false);
|
|
|
|
if (uiLayout *panel = uiLayoutPanel(C, layout, "OBJ_import_general", false, IFACE_("General"))) {
|
|
uiLayout *col = uiLayoutColumn(panel, false);
|
|
uiItemR(col, ptr, "global_scale", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
|
uiItemR(col, ptr, "clamp_size", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
|
uiItemR(col, ptr, "forward_axis", UI_ITEM_NONE, IFACE_("Forward Axis"), ICON_NONE);
|
|
uiItemR(col, ptr, "up_axis", UI_ITEM_NONE, IFACE_("Up Axis"), ICON_NONE);
|
|
}
|
|
|
|
if (uiLayout *panel = uiLayoutPanel(C, layout, "OBJ_import_options", false, IFACE_("Options"))) {
|
|
uiLayout *col = uiLayoutColumn(panel, false);
|
|
uiItemR(col, ptr, "use_split_objects", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
|
uiItemR(col, ptr, "use_split_groups", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
|
uiItemR(col, ptr, "import_vertex_groups", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
|
uiItemR(col, ptr, "validate_meshes", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
|
uiItemR(col, ptr, "close_spline_loops", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
|
uiItemR(col, ptr, "collection_separator", UI_ITEM_NONE, std::nullopt, 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 = blender::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");
|
|
|
|
/* 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 blender::ed::io {
|
|
void obj_file_handler_add()
|
|
{
|
|
auto fh = std::make_unique<blender::bke::FileHandlerType>();
|
|
STRNCPY(fh->idname, "IO_FH_obj");
|
|
STRNCPY(fh->import_operator, "WM_OT_obj_import");
|
|
STRNCPY(fh->export_operator, "WM_OT_obj_export");
|
|
STRNCPY(fh->label, "Wavefront OBJ");
|
|
STRNCPY(fh->file_extensions_str, ".obj");
|
|
fh->poll_drop = poll_file_object_drop;
|
|
bke::file_handler_add(std::move(fh));
|
|
}
|
|
} // namespace blender::ed::io
|
|
|
|
#endif /* WITH_IO_WAVEFRONT_OBJ */
|