GPv3: Import and export for SVG and PDF
Implements the SVG import/export and PDF export operators for GPv3. Pull Request: https://projects.blender.org/blender/blender/pulls/123996
This commit is contained in:
@@ -480,7 +480,7 @@ option(WITH_OPENCOLLADA "Enable OpenCollada Support (http://www.opencollada.org)
|
||||
option(WITH_IO_WAVEFRONT_OBJ "Enable Wavefront-OBJ 3D file format support (*.obj)" ON)
|
||||
option(WITH_IO_PLY "Enable PLY 3D file format support (*.ply)" ON)
|
||||
option(WITH_IO_STL "Enable STL 3D file format support (*.stl)" ON)
|
||||
option(WITH_IO_GPENCIL "Enable grease-pencil file format IO (*.svg, *.pdf)" ON)
|
||||
option(WITH_IO_GREASE_PENCIL "Enable grease-pencil file format IO (*.svg, *.pdf)" ON)
|
||||
|
||||
# Sound output
|
||||
option(WITH_SDL "Enable SDL for sound" ON)
|
||||
|
||||
@@ -37,7 +37,7 @@ set(WITH_INTERNATIONAL OFF CACHE BOOL "" FORCE)
|
||||
set(WITH_IO_PLY OFF CACHE BOOL "" FORCE)
|
||||
set(WITH_IO_STL OFF CACHE BOOL "" FORCE)
|
||||
set(WITH_IO_WAVEFRONT_OBJ OFF CACHE BOOL "" FORCE)
|
||||
set(WITH_IO_GPENCIL OFF CACHE BOOL "" FORCE)
|
||||
set(WITH_IO_GREASE_PENCIL OFF CACHE BOOL "" FORCE)
|
||||
set(WITH_JACK OFF CACHE BOOL "" FORCE)
|
||||
set(WITH_LIBMV OFF CACHE BOOL "" FORCE)
|
||||
set(WITH_LLVM OFF CACHE BOOL "" FORCE)
|
||||
|
||||
@@ -893,7 +893,6 @@ class WM_OT_operator_presets_cleanup(Operator):
|
||||
"WM_OT_collada_import",
|
||||
"WM_OT_gpencil_export_svg",
|
||||
"WM_OT_gpencil_export_pdf",
|
||||
"WM_OT_gpencil_export_svg",
|
||||
"WM_OT_gpencil_import_svg",
|
||||
"WM_OT_obj_export",
|
||||
"WM_OT_obj_import",
|
||||
|
||||
@@ -435,7 +435,7 @@ class TOPBAR_MT_file_import(Menu):
|
||||
"wm.usd_import", text="Universal Scene Description (.usd*)")
|
||||
|
||||
if bpy.app.build_options.io_gpencil:
|
||||
self.layout.operator("wm.gpencil_import_svg", text="SVG as Grease Pencil")
|
||||
self.layout.operator("wm.grease_pencil_import_svg", text="SVG as Grease Pencil")
|
||||
|
||||
if bpy.app.build_options.io_wavefront_obj:
|
||||
self.layout.operator("wm.obj_import", text="Wavefront (.obj)")
|
||||
@@ -462,10 +462,10 @@ class TOPBAR_MT_file_export(Menu):
|
||||
if bpy.app.build_options.io_gpencil:
|
||||
# PUGIXML library dependency.
|
||||
if bpy.app.build_options.pugixml:
|
||||
self.layout.operator("wm.gpencil_export_svg", text="Grease Pencil as SVG")
|
||||
self.layout.operator("wm.grease_pencil_export_svg", text="Grease Pencil as SVG")
|
||||
# HARU library dependency.
|
||||
if bpy.app.build_options.haru:
|
||||
self.layout.operator("wm.gpencil_export_pdf", text="Grease Pencil as PDF")
|
||||
self.layout.operator("wm.grease_pencil_export_pdf", text="Grease Pencil as PDF")
|
||||
|
||||
if bpy.app.build_options.io_wavefront_obj:
|
||||
self.layout.operator("wm.obj_export", text="Wavefront (.obj)")
|
||||
|
||||
@@ -10,7 +10,8 @@ set(INC
|
||||
../../io/alembic
|
||||
../../io/collada
|
||||
../../io/common
|
||||
../../io/gpencil
|
||||
../../io/gpencil_legacy
|
||||
../../io/grease_pencil
|
||||
../../io/ply
|
||||
../../io/stl
|
||||
../../io/usd
|
||||
@@ -29,9 +30,10 @@ set(SRC
|
||||
io_cache.cc
|
||||
io_collada.cc
|
||||
io_drop_import_file.cc
|
||||
io_gpencil_export.cc
|
||||
io_gpencil_import.cc
|
||||
io_gpencil_utils.cc
|
||||
io_gpencil_legacy_export.cc
|
||||
io_gpencil_legacy_import.cc
|
||||
io_gpencil_legacy_utils.cc
|
||||
io_grease_pencil.cc
|
||||
io_obj.cc
|
||||
io_ops.cc
|
||||
io_ply_ops.cc
|
||||
@@ -43,7 +45,8 @@ set(SRC
|
||||
io_cache.hh
|
||||
io_collada.hh
|
||||
io_drop_import_file.hh
|
||||
io_gpencil.hh
|
||||
io_gpencil_legacy.hh
|
||||
io_grease_pencil.hh
|
||||
io_obj.hh
|
||||
io_ops.hh
|
||||
io_ply_ops.hh
|
||||
@@ -90,11 +93,12 @@ if(WITH_IO_STL)
|
||||
add_definitions(-DWITH_IO_STL)
|
||||
endif()
|
||||
|
||||
if(WITH_IO_GPENCIL)
|
||||
if(WITH_IO_GREASE_PENCIL)
|
||||
list(APPEND LIB
|
||||
bf_gpencil
|
||||
bf_io_gpencil_legacy
|
||||
bf_io_grease_pencil
|
||||
)
|
||||
add_definitions(-DWITH_IO_GPENCIL)
|
||||
add_definitions(-DWITH_IO_GREASE_PENCIL)
|
||||
endif()
|
||||
|
||||
if(WITH_ALEMBIC)
|
||||
|
||||
@@ -24,7 +24,3 @@ void WM_OT_gpencil_export_pdf(wmOperatorType *ot);
|
||||
|
||||
ARegion *get_invoke_region(bContext *C);
|
||||
View3D *get_invoke_view3d(bContext *C);
|
||||
|
||||
namespace blender::ed::io {
|
||||
void gpencil_file_handler_add();
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
* \ingroup editor/io
|
||||
*/
|
||||
|
||||
#ifdef WITH_IO_GPENCIL
|
||||
#ifdef WITH_IO_GREASE_PENCIL
|
||||
|
||||
# include "BLI_path_util.h"
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
# include "WM_api.hh"
|
||||
# include "WM_types.hh"
|
||||
|
||||
# include "io_gpencil.hh"
|
||||
# include "io_gpencil_legacy.hh"
|
||||
|
||||
# include "gpencil_io.h"
|
||||
|
||||
@@ -391,4 +391,4 @@ void WM_OT_gpencil_export_pdf(wmOperatorType *ot)
|
||||
}
|
||||
# endif /* WITH_HARU */
|
||||
|
||||
#endif /* WITH_IO_GPENCIL */
|
||||
#endif /* WITH_IO_GREASE_PENCIL */
|
||||
@@ -6,7 +6,7 @@
|
||||
* \ingroup editor/io
|
||||
*/
|
||||
|
||||
#ifdef WITH_IO_GPENCIL
|
||||
#ifdef WITH_IO_GREASE_PENCIL
|
||||
|
||||
# include "BLI_path_util.h"
|
||||
# include "BLI_string.h"
|
||||
@@ -28,7 +28,7 @@
|
||||
# include "WM_api.hh"
|
||||
# include "WM_types.hh"
|
||||
|
||||
# include "io_gpencil.hh"
|
||||
# include "io_gpencil_legacy.hh"
|
||||
# include "io_utils.hh"
|
||||
|
||||
# include "gpencil_io.h"
|
||||
@@ -172,17 +172,4 @@ void WM_OT_gpencil_import_svg(wmOperatorType *ot)
|
||||
100.0f);
|
||||
}
|
||||
|
||||
namespace blender::ed::io {
|
||||
void gpencil_file_handler_add()
|
||||
{
|
||||
auto fh = std::make_unique<blender::bke::FileHandlerType>();
|
||||
STRNCPY(fh->idname, "IO_FH_gpencil_svg");
|
||||
STRNCPY(fh->import_operator, "WM_OT_gpencil_import_svg");
|
||||
STRNCPY(fh->label, "SVG as Grease Pencil");
|
||||
STRNCPY(fh->file_extensions_str, ".svg");
|
||||
fh->poll_drop = poll_file_object_drop;
|
||||
bke::file_handler_add(std::move(fh));
|
||||
}
|
||||
} // namespace blender::ed::io
|
||||
|
||||
#endif /* WITH_IO_GPENCIL */
|
||||
#endif /* WITH_IO_GREASE_PENCIL */
|
||||
@@ -6,7 +6,7 @@
|
||||
* \ingroup editor/io
|
||||
*/
|
||||
|
||||
#ifdef WITH_IO_GPENCIL
|
||||
#ifdef WITH_IO_GREASE_PENCIL
|
||||
|
||||
# include "DNA_space_types.h"
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
# include "WM_api.hh"
|
||||
|
||||
# include "io_gpencil.hh"
|
||||
# include "io_gpencil_legacy.hh"
|
||||
|
||||
ARegion *get_invoke_region(bContext *C)
|
||||
{
|
||||
@@ -50,4 +50,4 @@ View3D *get_invoke_view3d(bContext *C)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#endif /* WITH_IO_GPENCIL */
|
||||
#endif /* WITH_IO_GREASE_PENCIL */
|
||||
597
source/blender/editors/io/io_grease_pencil.cc
Normal file
597
source/blender/editors/io/io_grease_pencil.cc
Normal file
@@ -0,0 +1,597 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup editor/io
|
||||
*/
|
||||
|
||||
#include "DNA_view3d_types.h"
|
||||
#ifdef WITH_IO_GREASE_PENCIL
|
||||
|
||||
# include "BLI_path_util.h"
|
||||
# include "BLI_string.h"
|
||||
|
||||
# include "DNA_space_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_resources.hh"
|
||||
|
||||
# include "WM_api.hh"
|
||||
# include "WM_types.hh"
|
||||
|
||||
# include "io_grease_pencil.hh"
|
||||
# include "io_utils.hh"
|
||||
|
||||
# include "grease_pencil_io.hh"
|
||||
|
||||
# if defined(WITH_PUGIXML) || defined(WITH_HARU)
|
||||
|
||||
namespace blender::ed::io {
|
||||
|
||||
/* 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;
|
||||
|
||||
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},
|
||||
};
|
||||
|
||||
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");
|
||||
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");
|
||||
}
|
||||
|
||||
/* 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 blender::ed::io
|
||||
|
||||
# endif
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name SVG single frame import
|
||||
* \{ */
|
||||
|
||||
namespace blender::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 int 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, ®ion, &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 = true;
|
||||
|
||||
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 = blender::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)
|
||||
{
|
||||
uiLayout *layout = op->layout;
|
||||
uiLayoutSetPropSep(layout, true);
|
||||
uiLayoutSetPropDecorate(layout, false);
|
||||
uiLayout *box = uiLayoutBox(layout);
|
||||
uiLayout *col = uiLayoutColumn(box, false);
|
||||
uiItemR(col, op->ptr, "resolution", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
uiItemR(col, op->ptr, "scale", UI_ITEM_NONE, nullptr, 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 blender::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 = blender::ed::io::filesel_drop_import_invoke;
|
||||
ot->exec = blender::ed::io::grease_pencil_import_svg_exec;
|
||||
ot->poll = blender::ed::io::grease_pencil_import_svg_poll;
|
||||
ot->ui = blender::ed::io::grease_pencil_import_svg_draw;
|
||||
ot->check = blender::ed::io::grease_pencil_import_svg_check;
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name SVG single frame export
|
||||
* \{ */
|
||||
|
||||
# ifdef WITH_PUGIXML
|
||||
|
||||
namespace blender::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 int 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 int grease_pencil_export_svg_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, ®ion, &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::Active;
|
||||
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);
|
||||
const bool done = blender::io::grease_pencil::export_svg(io_context, params, *scene, filepath);
|
||||
WM_cursor_wait(false);
|
||||
|
||||
if (!done) {
|
||||
BKE_report(op->reports, RPT_WARNING, "Unable to export SVG");
|
||||
}
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
static void grease_pencil_export_svg_draw(bContext * /*C*/, wmOperator *op)
|
||||
{
|
||||
uiLayout *layout = op->layout;
|
||||
uiLayout *box, *row;
|
||||
|
||||
uiLayoutSetPropSep(layout, true);
|
||||
uiLayoutSetPropDecorate(layout, false);
|
||||
|
||||
box = uiLayoutBox(layout);
|
||||
|
||||
row = uiLayoutRow(box, false);
|
||||
uiItemL(row, IFACE_("Scene Options"), ICON_NONE);
|
||||
|
||||
row = uiLayoutRow(box, false);
|
||||
uiItemR(row, op->ptr, "selected_object_type", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
|
||||
box = uiLayoutBox(layout);
|
||||
row = uiLayoutRow(box, false);
|
||||
uiItemL(row, IFACE_("Export Options"), ICON_NONE);
|
||||
|
||||
uiLayout *col = uiLayoutColumn(box, false);
|
||||
uiItemR(col, op->ptr, "stroke_sample", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
uiItemR(col, op->ptr, "use_fill", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
uiItemR(col, op->ptr, "use_uniform_width", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
uiItemR(col, op->ptr, "use_clip_camera", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
}
|
||||
|
||||
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 blender::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 = blender::ed::io::grease_pencil_export_svg_invoke;
|
||||
ot->exec = blender::ed::io::grease_pencil_export_svg_exec;
|
||||
ot->poll = blender::ed::io::grease_pencil_export_svg_poll;
|
||||
ot->ui = blender::ed::io::grease_pencil_export_svg_draw;
|
||||
ot->check = blender::ed::io::grease_pencil_export_svg_check;
|
||||
|
||||
WM_operator_properties_filesel(ot,
|
||||
FILE_TYPE_FOLDER | FILE_TYPE_OBJECT_IO,
|
||||
FILE_BLENDER,
|
||||
FILE_SAVE,
|
||||
WM_FILESEL_FILEPATH | WM_FILESEL_SHOW_PROPS,
|
||||
FILE_DEFAULTDISPLAY,
|
||||
FILE_SORT_DEFAULT);
|
||||
|
||||
blender::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
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name PDF single frame export
|
||||
* \{ */
|
||||
|
||||
# ifdef WITH_HARU
|
||||
|
||||
namespace blender::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 int 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 int 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, ®ion, &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 ui_gpencil_export_pdf_settings(uiLayout *layout, PointerRNA *imfptr)
|
||||
{
|
||||
uiLayout *box, *row, *col, *sub;
|
||||
|
||||
uiLayoutSetPropSep(layout, true);
|
||||
uiLayoutSetPropDecorate(layout, false);
|
||||
|
||||
box = uiLayoutBox(layout);
|
||||
|
||||
row = uiLayoutRow(box, false);
|
||||
uiItemL(row, IFACE_("Scene Options"), ICON_NONE);
|
||||
|
||||
row = uiLayoutRow(box, false);
|
||||
uiItemR(row, imfptr, "selected_object_type", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
|
||||
box = uiLayoutBox(layout);
|
||||
row = uiLayoutRow(box, false);
|
||||
uiItemL(row, IFACE_("Export Options"), ICON_NONE);
|
||||
|
||||
col = uiLayoutColumn(box, false);
|
||||
sub = uiLayoutColumn(col, true);
|
||||
uiItemR(sub, imfptr, "frame_mode", UI_ITEM_NONE, IFACE_("Frame"), ICON_NONE);
|
||||
|
||||
uiLayoutSetPropSep(box, true);
|
||||
|
||||
sub = uiLayoutColumn(col, true);
|
||||
uiItemR(sub, imfptr, "stroke_sample", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
uiItemR(sub, imfptr, "use_fill", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
uiItemR(sub, imfptr, "use_uniform_width", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
}
|
||||
|
||||
static void grease_pencil_export_pdf_draw(bContext * /*C*/, wmOperator *op)
|
||||
{
|
||||
ui_gpencil_export_pdf_settings(op->layout, op->ptr);
|
||||
}
|
||||
|
||||
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 blender::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 = blender::ed::io::grease_pencil_export_pdf_invoke;
|
||||
ot->exec = blender::ed::io::grease_pencil_export_pdf_exec;
|
||||
ot->poll = blender::ed::io::grease_pencil_export_pdf_poll;
|
||||
ot->ui = blender::ed::io::grease_pencil_export_pdf_draw;
|
||||
ot->check = blender::ed::io::grease_pencil_export_pdf_check;
|
||||
|
||||
WM_operator_properties_filesel(ot,
|
||||
FILE_TYPE_FOLDER | FILE_TYPE_OBJECT_IO,
|
||||
FILE_BLENDER,
|
||||
FILE_SAVE,
|
||||
WM_FILESEL_FILEPATH | WM_FILESEL_SHOW_PROPS,
|
||||
FILE_DEFAULTDISPLAY,
|
||||
FILE_SORT_DEFAULT);
|
||||
|
||||
using blender::io::grease_pencil::ExportParams;
|
||||
|
||||
static const EnumPropertyItem frame_mode_items[] = {
|
||||
{int(ExportParams::FrameMode::Active), "ACTIVE", 0, "Active", "Include only active frame"},
|
||||
{int(ExportParams::FrameMode::Selected),
|
||||
"SELECTED",
|
||||
0,
|
||||
"Selected",
|
||||
"Include selected frames"},
|
||||
{int(ExportParams::FrameMode::Scene), "SCENE", 0, "Scene", "Include all scene frames"},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
blender::ed::io::grease_pencil_export_common_props_definition(ot);
|
||||
ot->prop = RNA_def_enum(ot->srna,
|
||||
"frame_mode",
|
||||
frame_mode_items,
|
||||
int(ExportParams::FrameMode::Active),
|
||||
"Frames",
|
||||
"Which frames to include in the export");
|
||||
}
|
||||
|
||||
# endif /* WITH_HARU */
|
||||
|
||||
/** \} */
|
||||
|
||||
namespace blender::ed::io {
|
||||
|
||||
void grease_pencil_file_handler_add()
|
||||
{
|
||||
auto fh = std::make_unique<blender::bke::FileHandlerType>();
|
||||
STRNCPY(fh->idname, "IO_FH_grease_pencil_svg");
|
||||
STRNCPY(fh->import_operator, "WM_OT_grease_pencil_import_svg");
|
||||
STRNCPY(fh->label, "SVG as Grease Pencil");
|
||||
STRNCPY(fh->file_extensions_str, ".svg");
|
||||
fh->poll_drop = poll_file_object_drop;
|
||||
bke::file_handler_add(std::move(fh));
|
||||
}
|
||||
|
||||
} // namespace blender::ed::io
|
||||
|
||||
#endif /* WITH_IO_GREASE_PENCIL */
|
||||
27
source/blender/editors/io/io_grease_pencil.hh
Normal file
27
source/blender/editors/io/io_grease_pencil.hh
Normal file
@@ -0,0 +1,27 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup editor/io
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
struct ARegion;
|
||||
struct View3D;
|
||||
struct bContext;
|
||||
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 blender::ed::io {
|
||||
void grease_pencil_file_handler_add();
|
||||
}
|
||||
@@ -24,7 +24,8 @@
|
||||
|
||||
#include "io_cache.hh"
|
||||
#include "io_drop_import_file.hh"
|
||||
#include "io_gpencil.hh"
|
||||
#include "io_gpencil_legacy.hh"
|
||||
#include "io_grease_pencil.hh"
|
||||
#include "io_obj.hh"
|
||||
#include "io_ply_ops.hh"
|
||||
#include "io_stl_ops.hh"
|
||||
@@ -49,14 +50,17 @@ void ED_operatortypes_io()
|
||||
ed::io::usd_file_handler_add();
|
||||
#endif
|
||||
|
||||
#ifdef WITH_IO_GPENCIL
|
||||
#ifdef WITH_IO_GREASE_PENCIL
|
||||
WM_operatortype_append(WM_OT_gpencil_import_svg);
|
||||
ed::io::gpencil_file_handler_add();
|
||||
WM_operatortype_append(WM_OT_grease_pencil_import_svg);
|
||||
ed::io::grease_pencil_file_handler_add();
|
||||
# ifdef WITH_PUGIXML
|
||||
WM_operatortype_append(WM_OT_gpencil_export_svg);
|
||||
WM_operatortype_append(WM_OT_grease_pencil_export_svg);
|
||||
# endif
|
||||
# ifdef WITH_HARU
|
||||
WM_operatortype_append(WM_OT_gpencil_export_pdf);
|
||||
WM_operatortype_append(WM_OT_grease_pencil_export_pdf);
|
||||
# endif
|
||||
#endif
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
if(WITH_IO_WAVEFRONT_OBJ OR WITH_IO_PLY OR WITH_IO_STL OR WITH_IO_GPENCIL OR WITH_ALEMBIC OR WITH_USD)
|
||||
if(WITH_IO_WAVEFRONT_OBJ OR WITH_IO_PLY OR WITH_IO_STL OR WITH_IO_GREASE_PENCIL OR WITH_ALEMBIC OR WITH_USD)
|
||||
add_subdirectory(common)
|
||||
endif()
|
||||
|
||||
@@ -18,8 +18,9 @@ if(WITH_IO_STL)
|
||||
add_subdirectory(stl)
|
||||
endif()
|
||||
|
||||
if(WITH_IO_GPENCIL)
|
||||
add_subdirectory(gpencil)
|
||||
if(WITH_IO_GREASE_PENCIL)
|
||||
add_subdirectory(gpencil_legacy)
|
||||
add_subdirectory(grease_pencil)
|
||||
endif()
|
||||
|
||||
if(WITH_ALEMBIC)
|
||||
|
||||
@@ -84,4 +84,4 @@ if(WITH_BOOST)
|
||||
)
|
||||
endif()
|
||||
|
||||
blender_add_lib(bf_gpencil "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
|
||||
blender_add_lib(bf_io_gpencil_legacy "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
|
||||
82
source/blender/io/grease_pencil/CMakeLists.txt
Normal file
82
source/blender/io/grease_pencil/CMakeLists.txt
Normal file
@@ -0,0 +1,82 @@
|
||||
# SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
set(INC
|
||||
.
|
||||
../common
|
||||
../../blenkernel
|
||||
../../blenloader
|
||||
../../bmesh
|
||||
../../editors/include
|
||||
../../functions
|
||||
../../geometry
|
||||
../../makesdna
|
||||
../../makesrna
|
||||
../../windowmanager
|
||||
../../../../intern/guardedalloc
|
||||
../../../../intern/utfconv
|
||||
../../../../extern/fmtlib/include
|
||||
)
|
||||
|
||||
set(INC_SYS
|
||||
)
|
||||
|
||||
set(SRC
|
||||
intern/grease_pencil_io_import_svg.cc
|
||||
intern/grease_pencil_io.cc
|
||||
|
||||
grease_pencil_io.hh
|
||||
intern/grease_pencil_io_intern.hh
|
||||
)
|
||||
|
||||
set(LIB
|
||||
bf_blenkernel
|
||||
PRIVATE bf::blenlib
|
||||
PRIVATE bf::depsgraph
|
||||
PRIVATE bf::dna
|
||||
PRIVATE bf::extern::nanosvg
|
||||
PRIVATE bf::intern::clog
|
||||
PRIVATE bf::intern::guardedalloc
|
||||
PRIVATE extern_fmtlib
|
||||
bf_io_common
|
||||
)
|
||||
|
||||
if(WITH_PUGIXML)
|
||||
list(APPEND SRC
|
||||
intern/grease_pencil_io_export_svg.cc
|
||||
)
|
||||
list(APPEND INC_SYS
|
||||
${PUGIXML_INCLUDE_DIR}
|
||||
)
|
||||
list(APPEND LIB
|
||||
${PUGIXML_LIBRARIES}
|
||||
)
|
||||
add_definitions(-DWITH_PUGIXML)
|
||||
endif()
|
||||
|
||||
if(WITH_HARU)
|
||||
list(APPEND SRC
|
||||
intern/grease_pencil_io_export_pdf.cc
|
||||
)
|
||||
list(APPEND INC_SYS
|
||||
${HARU_INCLUDE_DIRS}
|
||||
)
|
||||
list(APPEND LIB
|
||||
${HARU_LIBRARIES}
|
||||
|
||||
# Haru needs `TIFFFaxBlackCodes` & `TIFFFaxWhiteCodes` symbols from TIFF.
|
||||
# Can be removed with Haru 2.4.0. They should be shipping with their own
|
||||
# Fax codes defined by default from that version onwards.
|
||||
${TIFF_LIBRARY}
|
||||
)
|
||||
add_definitions(-DWITH_HARU)
|
||||
endif()
|
||||
|
||||
if(WITH_BOOST)
|
||||
list(APPEND LIB
|
||||
${BOOST_LIBRARIES}
|
||||
)
|
||||
endif()
|
||||
|
||||
blender_add_lib(bf_io_grease_pencil "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
|
||||
86
source/blender/io/grease_pencil/grease_pencil_io.hh
Normal file
86
source/blender/io/grease_pencil/grease_pencil_io.hh
Normal file
@@ -0,0 +1,86 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_string_ref.hh"
|
||||
|
||||
#include "DNA_view3d_types.h"
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup bgrease_pencil
|
||||
*/
|
||||
|
||||
struct ARegion;
|
||||
struct View3D;
|
||||
struct bContext;
|
||||
struct Scene;
|
||||
struct ReportList;
|
||||
struct Depsgraph;
|
||||
|
||||
namespace blender::io::grease_pencil {
|
||||
|
||||
struct IOContext {
|
||||
ReportList *reports;
|
||||
bContext &C;
|
||||
const ARegion *region;
|
||||
const View3D *v3d;
|
||||
const RegionView3D *rv3d;
|
||||
Scene *scene;
|
||||
Depsgraph *depsgraph;
|
||||
|
||||
IOContext(bContext &C,
|
||||
const ARegion *region,
|
||||
const View3D *v3d,
|
||||
const RegionView3D *rv3d,
|
||||
ReportList *reports);
|
||||
};
|
||||
|
||||
struct ImportParams {
|
||||
float scale = 1.0f;
|
||||
int frame_number = 1;
|
||||
int resolution = 10;
|
||||
bool use_scene_unit = false;
|
||||
bool recenter_bounds = false;
|
||||
};
|
||||
|
||||
struct ExportParams {
|
||||
/* Object to be exported. */
|
||||
enum class SelectMode {
|
||||
Active = 0,
|
||||
Selected = 1,
|
||||
Visible = 2,
|
||||
};
|
||||
|
||||
/** Frame-range to be exported. */
|
||||
enum class FrameMode {
|
||||
Active = 0,
|
||||
Selected = 1,
|
||||
Scene = 2,
|
||||
};
|
||||
|
||||
Object *object = nullptr;
|
||||
SelectMode select_mode = SelectMode::Active;
|
||||
FrameMode frame_mode = FrameMode::Active;
|
||||
bool export_stroke_materials = true;
|
||||
bool export_fill_materials = true;
|
||||
/* Clip drawings to camera size when exporting in camera view. */
|
||||
bool use_clip_camera = false;
|
||||
/* Enforce uniform stroke width by averaging radius. */
|
||||
bool use_uniform_width = false;
|
||||
/* Distance for resampling outline curves before export, disabled if zero. */
|
||||
float outline_resample_length = 0.0f;
|
||||
};
|
||||
|
||||
bool import_svg(const IOContext &context, const ImportParams ¶ms, StringRefNull filepath);
|
||||
bool export_svg(const IOContext &context,
|
||||
const ExportParams ¶ms,
|
||||
Scene &scene,
|
||||
StringRefNull filepath);
|
||||
bool export_pdf(const IOContext &context,
|
||||
const ExportParams ¶ms,
|
||||
Scene &scene,
|
||||
StringRefNull filepath);
|
||||
|
||||
} // namespace blender::io::grease_pencil
|
||||
578
source/blender/io/grease_pencil/intern/grease_pencil_io.cc
Normal file
578
source/blender/io/grease_pencil/intern/grease_pencil_io.cc
Normal file
@@ -0,0 +1,578 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_bounds.hh"
|
||||
#include "BLI_color.hh"
|
||||
#include "BLI_math_matrix.hh"
|
||||
#include "BLI_math_vector.h"
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_camera.h"
|
||||
#include "BKE_context.hh"
|
||||
#include "BKE_crazyspace.hh"
|
||||
#include "BKE_curves.hh"
|
||||
#include "BKE_gpencil_legacy.h"
|
||||
#include "BKE_grease_pencil.hh"
|
||||
#include "BKE_layer.hh"
|
||||
#include "BKE_material.h"
|
||||
#include "BKE_scene.hh"
|
||||
|
||||
#include "BLI_math_vector.hh"
|
||||
#include "DNA_grease_pencil_types.h"
|
||||
#include "DNA_material_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_view3d_types.h"
|
||||
|
||||
#include "DEG_depsgraph_query.hh"
|
||||
|
||||
#include "GEO_resample_curves.hh"
|
||||
|
||||
#include "ED_grease_pencil.hh"
|
||||
#include "ED_object.hh"
|
||||
#include "ED_view3d.hh"
|
||||
|
||||
#include "grease_pencil_io_intern.hh"
|
||||
#include <optional>
|
||||
|
||||
/** \file
|
||||
* \ingroup bgrease_pencil
|
||||
*/
|
||||
|
||||
namespace blender::io::grease_pencil {
|
||||
|
||||
static float get_average(const Span<float> values)
|
||||
{
|
||||
return values.is_empty() ? 0.0f :
|
||||
std::accumulate(values.begin(), values.end(), 0.0f) / values.size();
|
||||
}
|
||||
|
||||
static ColorGeometry4f get_average(const Span<ColorGeometry4f> values)
|
||||
{
|
||||
if (values.is_empty()) {
|
||||
return ColorGeometry4f(0);
|
||||
}
|
||||
/* ColorGeometry4f does not support arithmetic directly. */
|
||||
Span<float4> rgba_values = values.cast<float4>();
|
||||
float4 avg_rgba = std::accumulate(rgba_values.begin(), rgba_values.end(), float4(0)) /
|
||||
values.size();
|
||||
return ColorGeometry4f(avg_rgba);
|
||||
}
|
||||
|
||||
static std::optional<float> try_get_constant_value(const VArray<float> values,
|
||||
const float epsilon = 1e-5f)
|
||||
{
|
||||
if (values.is_empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const float first_value = values.first();
|
||||
const std::optional<float> first_value_opt = std::make_optional(first_value);
|
||||
return threading::parallel_reduce(
|
||||
values.index_range().drop_front(1),
|
||||
4096,
|
||||
first_value_opt,
|
||||
[&](const IndexRange range, const std::optional<float> /*value*/) -> std::optional<float> {
|
||||
for (const int i : range) {
|
||||
if (math::abs(values[i] - first_value) > epsilon) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
return first_value_opt;
|
||||
},
|
||||
[&](const std::optional<float> a, const std::optional<float> b) {
|
||||
return (a && b) ? first_value_opt : std::nullopt;
|
||||
});
|
||||
}
|
||||
|
||||
IOContext::IOContext(bContext &C,
|
||||
const ARegion *region,
|
||||
const View3D *v3d,
|
||||
const RegionView3D *rv3d,
|
||||
ReportList *reports)
|
||||
: reports(reports),
|
||||
C(C),
|
||||
region(region),
|
||||
v3d(v3d),
|
||||
rv3d(rv3d),
|
||||
scene(CTX_data_scene(&C)),
|
||||
depsgraph(CTX_data_depsgraph_pointer(&C))
|
||||
{
|
||||
}
|
||||
|
||||
GreasePencilImporter::GreasePencilImporter(const IOContext &context, const ImportParams ¶ms)
|
||||
: context_(context), params_(params)
|
||||
{
|
||||
}
|
||||
|
||||
Object *GreasePencilImporter::create_object(const StringRefNull name)
|
||||
{
|
||||
const float3 cur_loc = context_.scene->cursor.location;
|
||||
const float3 rot = float3(0.0f);
|
||||
const ushort local_view_bits = (context_.v3d && context_.v3d->localvd) ?
|
||||
context_.v3d->local_view_uid :
|
||||
ushort(0);
|
||||
|
||||
Object *ob_gpencil = blender::ed::object::add_type(
|
||||
&context_.C, OB_GREASE_PENCIL, name.c_str(), cur_loc, rot, false, local_view_bits);
|
||||
|
||||
return ob_gpencil;
|
||||
}
|
||||
|
||||
int GreasePencilImporter::create_material(const StringRefNull name,
|
||||
const bool stroke,
|
||||
const bool fill)
|
||||
{
|
||||
const ColorGeometry4f default_stroke_color = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
const ColorGeometry4f default_fill_color = {0.5f, 0.5f, 0.5f, 1.0f};
|
||||
int mat_index = BKE_gpencil_object_material_index_get_by_name(object_, name.c_str());
|
||||
/* Stroke and Fill material. */
|
||||
if (mat_index == -1) {
|
||||
Main *bmain = CTX_data_main(&context_.C);
|
||||
int new_idx;
|
||||
Material *mat_gp = BKE_gpencil_object_material_new(bmain, object_, name.c_str(), &new_idx);
|
||||
MaterialGPencilStyle *gp_style = mat_gp->gp_style;
|
||||
gp_style->flag &= ~GP_MATERIAL_STROKE_SHOW;
|
||||
gp_style->flag &= ~GP_MATERIAL_FILL_SHOW;
|
||||
|
||||
copy_v4_v4(gp_style->stroke_rgba, default_stroke_color);
|
||||
copy_v4_v4(gp_style->fill_rgba, default_fill_color);
|
||||
if (stroke) {
|
||||
gp_style->flag |= GP_MATERIAL_STROKE_SHOW;
|
||||
}
|
||||
if (fill) {
|
||||
gp_style->flag |= GP_MATERIAL_FILL_SHOW;
|
||||
}
|
||||
mat_index = object_->totcol - 1;
|
||||
}
|
||||
|
||||
return mat_index;
|
||||
}
|
||||
|
||||
GreasePencilExporter::GreasePencilExporter(const IOContext &context, const ExportParams ¶ms)
|
||||
: context_(context), params_(params)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr const char *attr_material_index = "material_index";
|
||||
|
||||
static IndexMask get_visible_strokes(const Object &object,
|
||||
const bke::greasepencil::Drawing &drawing,
|
||||
IndexMaskMemory &memory)
|
||||
{
|
||||
const bke::CurvesGeometry &strokes = drawing.strokes();
|
||||
const bke::AttributeAccessor attributes = strokes.attributes();
|
||||
const VArray<int> materials = *attributes.lookup<int>(attr_material_index,
|
||||
bke::AttrDomain::Curve);
|
||||
|
||||
auto is_visible_curve = [&](const int curve_i) {
|
||||
/* Check if stroke can be drawn. */
|
||||
const IndexRange points = strokes.points_by_curve()[curve_i];
|
||||
if (points.size() < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check if the material is visible. */
|
||||
const Material *material = BKE_object_material_get(const_cast<Object *>(&object),
|
||||
materials[curve_i] + 1);
|
||||
const MaterialGPencilStyle *gp_style = material ? material->gp_style : nullptr;
|
||||
const bool is_hidden_material = (gp_style->flag & GP_MATERIAL_HIDE);
|
||||
const bool is_stroke_material = (gp_style->flag & GP_MATERIAL_STROKE_SHOW);
|
||||
if (gp_style == nullptr || is_hidden_material || !is_stroke_material) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
return IndexMask::from_predicate(
|
||||
strokes.curves_range(), GrainSize(512), memory, is_visible_curve);
|
||||
}
|
||||
|
||||
static std::optional<Bounds<float2>> compute_drawing_bounds(
|
||||
const ARegion ®ion,
|
||||
const RegionView3D &rv3d,
|
||||
const Object &object,
|
||||
const Object &object_eval,
|
||||
const int layer_index,
|
||||
const int frame_number,
|
||||
const bke::greasepencil::Drawing &drawing)
|
||||
{
|
||||
using bke::greasepencil::Drawing;
|
||||
using bke::greasepencil::Layer;
|
||||
|
||||
std::optional<Bounds<float2>> drawing_bounds = std::nullopt;
|
||||
|
||||
BLI_assert(object.type == OB_GREASE_PENCIL);
|
||||
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
|
||||
if (!grease_pencil.has_active_layer()) {
|
||||
return drawing_bounds;
|
||||
}
|
||||
|
||||
const Layer &layer = *grease_pencil.layers()[layer_index];
|
||||
const float4x4 layer_to_world = layer.to_world_space(object);
|
||||
const bke::crazyspace::GeometryDeformation deformation =
|
||||
bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation(
|
||||
&object_eval, object, layer_index, frame_number);
|
||||
const VArray<float> radii = drawing.radii();
|
||||
const bke::CurvesGeometry &strokes = drawing.strokes();
|
||||
|
||||
IndexMaskMemory curve_mask_memory;
|
||||
const IndexMask curve_mask = get_visible_strokes(object, drawing, curve_mask_memory);
|
||||
|
||||
curve_mask.foreach_index(GrainSize(512), [&](const int curve_i) {
|
||||
const IndexRange points = strokes.points_by_curve()[curve_i];
|
||||
/* Check if stroke can be drawn. */
|
||||
if (points.size() < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const int point_i : points) {
|
||||
const float3 pos_world = math::transform_point(layer_to_world,
|
||||
deformation.positions[point_i]);
|
||||
float2 pos_view;
|
||||
eV3DProjStatus result = ED_view3d_project_float_global(
|
||||
®ion, pos_world, pos_view, V3D_PROJ_TEST_NOP);
|
||||
if (result == V3D_PROJ_RET_OK) {
|
||||
const float pixels = radii[point_i] / ED_view3d_pixel_size(&rv3d, pos_world);
|
||||
|
||||
std::optional<Bounds<float2>> point_bounds = Bounds<float2>(pos_view);
|
||||
point_bounds->pad(pixels);
|
||||
drawing_bounds = bounds::merge(drawing_bounds, point_bounds);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return drawing_bounds;
|
||||
}
|
||||
|
||||
static std::optional<Bounds<float2>> compute_objects_bounds(
|
||||
const ARegion ®ion,
|
||||
const RegionView3D &rv3d,
|
||||
const Depsgraph &depsgraph,
|
||||
const Span<GreasePencilExporter::ObjectInfo> objects,
|
||||
const int frame_number)
|
||||
{
|
||||
using bke::greasepencil::Drawing;
|
||||
using bke::greasepencil::Layer;
|
||||
using ObjectInfo = GreasePencilExporter::ObjectInfo;
|
||||
|
||||
constexpr float gap = 10.0f;
|
||||
|
||||
std::optional<Bounds<float2>> full_bounds = std::nullopt;
|
||||
|
||||
for (const ObjectInfo &info : objects) {
|
||||
const Object *object_eval = DEG_get_evaluated_object(&depsgraph, info.object);
|
||||
const GreasePencil &grease_pencil_eval = *static_cast<GreasePencil *>(object_eval->data);
|
||||
|
||||
for (const int layer_index : grease_pencil_eval.layers().index_range()) {
|
||||
const Layer &layer = *grease_pencil_eval.layers()[layer_index];
|
||||
const Drawing *drawing = grease_pencil_eval.get_drawing_at(layer, frame_number);
|
||||
if (drawing == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::optional<Bounds<float2>> layer_bounds = compute_drawing_bounds(
|
||||
region, rv3d, *info.object, *object_eval, layer_index, frame_number, *drawing);
|
||||
|
||||
full_bounds = bounds::merge(full_bounds, layer_bounds);
|
||||
}
|
||||
}
|
||||
|
||||
/* Add small gap. */
|
||||
full_bounds->pad(gap);
|
||||
|
||||
return full_bounds;
|
||||
}
|
||||
|
||||
void GreasePencilExporter::prepare_camera_params(Scene &scene,
|
||||
const int frame_number,
|
||||
const bool force_camera_view)
|
||||
{
|
||||
const bool use_camera_view = force_camera_view && (context_.v3d->camera != nullptr);
|
||||
|
||||
/* Ensure camera switch is applied. */
|
||||
BKE_scene_camera_switch_update(&scene);
|
||||
|
||||
/* Calculate camera matrix. */
|
||||
Object *cam_ob = scene.camera;
|
||||
if (cam_ob != nullptr) {
|
||||
/* Set up parameters. */
|
||||
CameraParams params;
|
||||
BKE_camera_params_init(¶ms);
|
||||
BKE_camera_params_from_object(¶ms, cam_ob);
|
||||
|
||||
/* Compute matrix, view-plane, etc. */
|
||||
BKE_camera_params_compute_viewplane(
|
||||
¶ms, scene.r.xsch, scene.r.ysch, scene.r.xasp, scene.r.yasp);
|
||||
BKE_camera_params_compute_matrix(¶ms);
|
||||
|
||||
float4x4 viewmat = math::invert(cam_ob->object_to_world());
|
||||
persmat_ = float4x4(params.winmat) * viewmat;
|
||||
}
|
||||
else {
|
||||
persmat_ = float4x4::identity();
|
||||
}
|
||||
|
||||
win_size_ = {context_.region->winx, context_.region->winy};
|
||||
|
||||
/* Camera rectangle. */
|
||||
if ((context_.rv3d->persp == RV3D_CAMOB) || (use_camera_view)) {
|
||||
BKE_render_resolution(&scene.r, false, &render_size_.x, &render_size_.y);
|
||||
|
||||
ED_view3d_calc_camera_border(&scene,
|
||||
context_.depsgraph,
|
||||
context_.region,
|
||||
context_.v3d,
|
||||
context_.rv3d,
|
||||
&camera_rect_,
|
||||
true);
|
||||
is_camera_ = true;
|
||||
camera_ratio_ = render_size_.x / (camera_rect_.xmax - camera_rect_.xmin);
|
||||
offset_.x = camera_rect_.xmin;
|
||||
offset_.y = camera_rect_.ymin;
|
||||
}
|
||||
else {
|
||||
is_camera_ = false;
|
||||
|
||||
Vector<ObjectInfo> objects = this->retrieve_objects();
|
||||
std::optional<Bounds<float2>> full_bounds = compute_objects_bounds(
|
||||
*context_.region, *context_.rv3d, *context_.depsgraph, objects, frame_number);
|
||||
if (full_bounds) {
|
||||
render_size_ = int2(full_bounds->size());
|
||||
offset_ = full_bounds->min;
|
||||
}
|
||||
else {
|
||||
camera_ratio_ = 1.0f;
|
||||
offset_ = float2(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColorGeometry4f GreasePencilExporter::compute_average_stroke_color(
|
||||
const Material &material, const Span<ColorGeometry4f> vertex_colors)
|
||||
{
|
||||
const MaterialGPencilStyle &gp_style = *material.gp_style;
|
||||
|
||||
const ColorGeometry4f material_color = ColorGeometry4f(gp_style.stroke_rgba);
|
||||
const ColorGeometry4f avg_vertex_color = get_average(vertex_colors);
|
||||
return math::interpolate(material_color, avg_vertex_color, avg_vertex_color.a);
|
||||
}
|
||||
|
||||
float GreasePencilExporter::compute_average_stroke_opacity(const Span<float> opacities)
|
||||
{
|
||||
return get_average(opacities);
|
||||
}
|
||||
|
||||
std::optional<float> GreasePencilExporter::try_get_uniform_point_width(
|
||||
const RegionView3D &rv3d, const Span<float3> world_positions, const Span<float> radii)
|
||||
{
|
||||
VArray<float> widths = VArray<float>::ForFunc(world_positions.size(), [&](const int index) {
|
||||
const float3 &pos = world_positions[index];
|
||||
const float radius = radii[index];
|
||||
return 2.0f * radius * ED_view3d_pixel_size(&rv3d, pos);
|
||||
});
|
||||
return try_get_constant_value(widths);
|
||||
}
|
||||
|
||||
Vector<GreasePencilExporter::ObjectInfo> GreasePencilExporter::retrieve_objects() const
|
||||
{
|
||||
using SelectMode = ExportParams::SelectMode;
|
||||
|
||||
Scene &scene = *CTX_data_scene(&context_.C);
|
||||
ViewLayer *view_layer = CTX_data_view_layer(&context_.C);
|
||||
const float3 camera_z_axis = float3(context_.rv3d->viewinv[2]);
|
||||
|
||||
BKE_view_layer_synced_ensure(&scene, view_layer);
|
||||
|
||||
Vector<ObjectInfo> objects;
|
||||
auto add_object = [&](Object *object) {
|
||||
if (object == nullptr || object->type != OB_GREASE_PENCIL) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float3 position = object->object_to_world().location();
|
||||
|
||||
/* Save z-depth from view to sort from back to front. */
|
||||
const bool use_ortho_depth = is_camera_ || !context_.rv3d->is_persp;
|
||||
const float depth = use_ortho_depth ? math::dot(camera_z_axis, position) :
|
||||
-ED_view3d_calc_zfac(context_.rv3d, position);
|
||||
objects.append({object, depth});
|
||||
};
|
||||
|
||||
switch (params_.select_mode) {
|
||||
case SelectMode::Active:
|
||||
add_object(params_.object);
|
||||
break;
|
||||
case SelectMode::Selected:
|
||||
LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) {
|
||||
if (base->flag & BASE_SELECTED) {
|
||||
add_object(base->object);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SelectMode::Visible:
|
||||
LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) {
|
||||
add_object(base->object);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Sort list of objects from point of view. */
|
||||
std::sort(objects.begin(), objects.end(), [](const ObjectInfo &info1, const ObjectInfo &info2) {
|
||||
return info1.depth < info2.depth;
|
||||
});
|
||||
|
||||
return objects;
|
||||
}
|
||||
|
||||
void GreasePencilExporter::foreach_stroke_in_layer(const Object &object,
|
||||
const bke::greasepencil::Layer &layer,
|
||||
const bke::greasepencil::Drawing &drawing,
|
||||
WriteStrokeFn stroke_fn)
|
||||
{
|
||||
using bke::greasepencil::Drawing;
|
||||
|
||||
const float4x4 layer_to_world = layer.to_world_space(object);
|
||||
const float4x4 viewmat = float4x4(context_.rv3d->viewmat);
|
||||
const float4x4 layer_to_view = viewmat * layer_to_world;
|
||||
|
||||
const bke::CurvesGeometry &curves = drawing.strokes();
|
||||
const bke::AttributeAccessor attributes = curves.attributes();
|
||||
/* Curve attributes. */
|
||||
const OffsetIndices points_by_curve = curves.points_by_curve();
|
||||
const VArray<bool> cyclic = curves.cyclic();
|
||||
const VArraySpan<int> material_indices = *attributes.lookup_or_default<int>(
|
||||
"material_index", bke::AttrDomain::Curve, 0);
|
||||
const VArraySpan<ColorGeometry4f> fill_colors = drawing.fill_colors();
|
||||
const VArray<int8_t> start_caps = *attributes.lookup_or_default<int8_t>(
|
||||
"start_cap", bke::AttrDomain::Curve, GP_STROKE_CAP_TYPE_ROUND);
|
||||
const VArray<int8_t> end_caps = *attributes.lookup_or_default<int8_t>(
|
||||
"end_cap", bke::AttrDomain::Curve, 0);
|
||||
/* Point attributes. */
|
||||
const Span<float3> positions = curves.positions();
|
||||
const VArraySpan<float> radii = drawing.radii();
|
||||
const VArraySpan<float> opacities = drawing.opacities();
|
||||
const VArraySpan<ColorGeometry4f> vertex_colors = drawing.vertex_colors();
|
||||
|
||||
Array<float3> world_positions(positions.size());
|
||||
threading::parallel_for(positions.index_range(), 4096, [&](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
world_positions[i] = math::transform_point(layer_to_world, positions[i]);
|
||||
}
|
||||
});
|
||||
|
||||
for (const int i_curve : curves.curves_range()) {
|
||||
const IndexRange points = points_by_curve[i_curve];
|
||||
if (points.size() < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool is_cyclic = cyclic[i_curve];
|
||||
const int material_index = material_indices[i_curve];
|
||||
const Material *material = BKE_object_material_get(const_cast<Object *>(&object),
|
||||
material_index + 1);
|
||||
BLI_assert(material->gp_style != nullptr);
|
||||
if (material->gp_style->flag & GP_MATERIAL_HIDE) {
|
||||
continue;
|
||||
}
|
||||
const bool is_stroke_material = material->gp_style->flag & GP_MATERIAL_STROKE_SHOW;
|
||||
const bool is_fill_material = material->gp_style->flag & GP_MATERIAL_FILL_SHOW;
|
||||
|
||||
/* Fill. */
|
||||
if (is_fill_material && params_.export_fill_materials) {
|
||||
const ColorGeometry4f material_fill_color = ColorGeometry4f(material->gp_style->fill_rgba);
|
||||
const ColorGeometry4f fill_color = math::interpolate(
|
||||
material_fill_color, fill_colors[i_curve], fill_colors[i_curve].a);
|
||||
stroke_fn(positions.slice(points),
|
||||
is_cyclic,
|
||||
fill_color,
|
||||
layer.opacity,
|
||||
std::nullopt,
|
||||
false,
|
||||
false);
|
||||
}
|
||||
|
||||
/* Stroke. */
|
||||
if (is_stroke_material && params_.export_stroke_materials) {
|
||||
const ColorGeometry4f stroke_color = compute_average_stroke_color(
|
||||
*material, vertex_colors.slice(points));
|
||||
const float stroke_opacity = compute_average_stroke_opacity(opacities.slice(points)) *
|
||||
layer.opacity;
|
||||
const std::optional<float> uniform_width = params_.use_uniform_width ?
|
||||
try_get_uniform_point_width(
|
||||
*context_.rv3d,
|
||||
world_positions.as_span().slice(points),
|
||||
radii.slice(points)) :
|
||||
std::nullopt;
|
||||
if (uniform_width) {
|
||||
const GreasePencilStrokeCapType start_cap = GreasePencilStrokeCapType(start_caps[i_curve]);
|
||||
const GreasePencilStrokeCapType end_cap = GreasePencilStrokeCapType(end_caps[i_curve]);
|
||||
const bool round_cap = start_cap == GP_STROKE_CAP_TYPE_ROUND ||
|
||||
end_cap == GP_STROKE_CAP_TYPE_ROUND;
|
||||
|
||||
stroke_fn(positions.slice(points),
|
||||
is_cyclic,
|
||||
stroke_color,
|
||||
stroke_opacity,
|
||||
uniform_width,
|
||||
round_cap,
|
||||
false);
|
||||
}
|
||||
else {
|
||||
const IndexMask single_curve_mask = IndexRange::from_single(i_curve);
|
||||
|
||||
constexpr int corner_subdivisions = 3;
|
||||
constexpr float outline_radius = 0.0f;
|
||||
constexpr float outline_offset = 0.0f;
|
||||
bke::CurvesGeometry outline = ed::greasepencil::create_curves_outline(drawing,
|
||||
single_curve_mask,
|
||||
layer_to_view,
|
||||
corner_subdivisions,
|
||||
outline_radius,
|
||||
outline_offset,
|
||||
material_index);
|
||||
|
||||
/* Sample the outline stroke. */
|
||||
if (params_.outline_resample_length > 0.0f) {
|
||||
VArray<float> resample_lengths = VArray<float>::ForSingle(
|
||||
params_.outline_resample_length, curves.curves_num());
|
||||
outline = geometry::resample_to_length(outline, single_curve_mask, resample_lengths);
|
||||
}
|
||||
|
||||
const OffsetIndices outline_points_by_curve = outline.points_by_curve();
|
||||
const Span<float3> outline_positions = outline.positions();
|
||||
|
||||
for (const int i_outline_curve : outline.curves_range()) {
|
||||
const IndexRange outline_points = outline_points_by_curve[i_outline_curve];
|
||||
/* Use stroke color to fill the outline. */
|
||||
stroke_fn(outline_positions.slice(outline_points),
|
||||
true,
|
||||
stroke_color,
|
||||
stroke_opacity,
|
||||
std::nullopt,
|
||||
false,
|
||||
true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float2 GreasePencilExporter::project_to_screen(const float4x4 &transform,
|
||||
const float3 &position) const
|
||||
{
|
||||
float2 screen_co = float2(0.0f);
|
||||
if (ED_view3d_project_float_ex(context_.region,
|
||||
const_cast<float(*)[4]>(context_.rv3d->winmat),
|
||||
false,
|
||||
math::transform_point(transform, position),
|
||||
screen_co,
|
||||
V3D_PROJ_TEST_NOP) == V3D_PROJ_RET_OK)
|
||||
{
|
||||
return screen_co;
|
||||
}
|
||||
return float2(0.0f);
|
||||
}
|
||||
|
||||
} // namespace blender::io::grease_pencil
|
||||
@@ -0,0 +1,288 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_curves.hh"
|
||||
#include "BKE_grease_pencil.hh"
|
||||
#include "BKE_material.h"
|
||||
#include "BKE_scene.hh"
|
||||
|
||||
#include "DEG_depsgraph_query.hh"
|
||||
|
||||
#include "DNA_grease_pencil_types.h"
|
||||
#include "DNA_material_types.h"
|
||||
#include "DNA_scene_types.h"
|
||||
|
||||
#include "ED_view3d.hh"
|
||||
|
||||
#include "grease_pencil_io.hh"
|
||||
#include "grease_pencil_io_intern.hh"
|
||||
|
||||
#include "hpdf.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
/** \file
|
||||
* \ingroup bgrease_pencil
|
||||
*/
|
||||
|
||||
namespace blender::io::grease_pencil {
|
||||
|
||||
class PDFExporter : public GreasePencilExporter {
|
||||
public:
|
||||
using GreasePencilExporter::GreasePencilExporter;
|
||||
|
||||
HPDF_Doc pdf_;
|
||||
HPDF_Page page_;
|
||||
|
||||
bool export_scene(Scene &scene, StringRefNull filepath);
|
||||
void export_grease_pencil_objects(int frame_number);
|
||||
void export_grease_pencil_layer(const Object &object,
|
||||
const bke::greasepencil::Layer &layer,
|
||||
const bke::greasepencil::Drawing &drawing);
|
||||
|
||||
bool create_document();
|
||||
bool add_page();
|
||||
|
||||
void write_stroke_to_polyline(const float4x4 &transform,
|
||||
const Span<float3> positions,
|
||||
const bool cyclic,
|
||||
const ColorGeometry4f &color,
|
||||
const float opacity,
|
||||
std::optional<float> width);
|
||||
bool write_to_file(StringRefNull filepath);
|
||||
};
|
||||
|
||||
static bool is_selected_frame(const GreasePencil &grease_pencil, const int frame_number)
|
||||
{
|
||||
for (const bke::greasepencil::Layer *layer : grease_pencil.layers()) {
|
||||
if (layer->is_visible()) {
|
||||
const GreasePencilFrame *frame = layer->frame_at(frame_number);
|
||||
if (frame->is_selected()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PDFExporter::export_scene(Scene &scene, StringRefNull filepath)
|
||||
{
|
||||
bool result = false;
|
||||
Object &ob_eval = *DEG_get_evaluated_object(context_.depsgraph, params_.object);
|
||||
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(ob_eval.data);
|
||||
|
||||
if (!create_document()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (params_.frame_mode) {
|
||||
case ExportParams::FrameMode::Active: {
|
||||
const int frame_number = scene.r.cfra;
|
||||
|
||||
this->prepare_camera_params(scene, frame_number, true);
|
||||
this->add_page();
|
||||
this->export_grease_pencil_objects(frame_number);
|
||||
result = this->write_to_file(filepath);
|
||||
break;
|
||||
}
|
||||
case ExportParams::FrameMode::Selected: {
|
||||
case ExportParams::FrameMode::Scene:
|
||||
const bool only_selected = (params_.frame_mode == ExportParams::FrameMode::Selected);
|
||||
const int orig_frame = scene.r.cfra;
|
||||
for (int frame_number = scene.r.sfra; frame_number <= scene.r.efra; frame_number++) {
|
||||
if (only_selected && !is_selected_frame(grease_pencil, frame_number)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
scene.r.cfra = frame_number;
|
||||
BKE_scene_graph_update_for_newframe(context_.depsgraph);
|
||||
|
||||
this->prepare_camera_params(scene, frame_number, true);
|
||||
this->add_page();
|
||||
this->export_grease_pencil_objects(frame_number);
|
||||
}
|
||||
|
||||
result = this->write_to_file(filepath);
|
||||
|
||||
/* Back to original frame. */
|
||||
scene.r.cfra = orig_frame;
|
||||
BKE_scene_camera_switch_update(&scene);
|
||||
BKE_scene_graph_update_for_newframe(context_.depsgraph);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void PDFExporter::export_grease_pencil_objects(const int frame_number)
|
||||
{
|
||||
using bke::greasepencil::Drawing;
|
||||
|
||||
Vector<ObjectInfo> objects = retrieve_objects();
|
||||
|
||||
for (const ObjectInfo &info : objects) {
|
||||
const Object *ob = info.object;
|
||||
|
||||
/* Use evaluated version to get strokes with modifiers. */
|
||||
Object *ob_eval = DEG_get_evaluated_object(context_.depsgraph, const_cast<Object *>(ob));
|
||||
BLI_assert(ob_eval->type == OB_GREASE_PENCIL);
|
||||
const GreasePencil *grease_pencil_eval = static_cast<const GreasePencil *>(ob_eval->data);
|
||||
|
||||
for (const bke::greasepencil::Layer *layer : grease_pencil_eval->layers()) {
|
||||
if (!layer->is_visible()) {
|
||||
return;
|
||||
}
|
||||
const Drawing *drawing = grease_pencil_eval->get_drawing_at(*layer, frame_number);
|
||||
if (drawing == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
export_grease_pencil_layer(*ob_eval, *layer, *drawing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PDFExporter::export_grease_pencil_layer(const Object &object,
|
||||
const bke::greasepencil::Layer &layer,
|
||||
const bke::greasepencil::Drawing &drawing)
|
||||
{
|
||||
using bke::greasepencil::Drawing;
|
||||
|
||||
const float4x4 layer_to_world = layer.to_world_space(object);
|
||||
const float4x4 viewmat = float4x4(context_.rv3d->viewmat);
|
||||
const float4x4 layer_to_view = viewmat * layer_to_world;
|
||||
|
||||
auto write_stroke = [&](const Span<float3> positions,
|
||||
const bool cyclic,
|
||||
const ColorGeometry4f &color,
|
||||
const float opacity,
|
||||
const std::optional<float> width,
|
||||
const bool /*round_cap*/,
|
||||
const bool /*is_outline*/) {
|
||||
write_stroke_to_polyline(layer_to_view, positions, cyclic, color, opacity, width);
|
||||
};
|
||||
|
||||
foreach_stroke_in_layer(object, layer, drawing, write_stroke);
|
||||
}
|
||||
|
||||
bool PDFExporter::create_document()
|
||||
{
|
||||
auto hpdf_error_handler = [](HPDF_STATUS error_no, HPDF_STATUS detail_no, void * /*user_data*/) {
|
||||
printf("ERROR: error_no=%04X, detail_no=%u\n", (HPDF_UINT)error_no, (HPDF_UINT)detail_no);
|
||||
};
|
||||
|
||||
pdf_ = HPDF_New(hpdf_error_handler, nullptr);
|
||||
if (!pdf_) {
|
||||
std::cout << "error: cannot create PdfDoc object\n";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PDFExporter::add_page()
|
||||
{
|
||||
page_ = HPDF_AddPage(pdf_);
|
||||
if (!pdf_) {
|
||||
std::cout << "error: cannot create PdfPage\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
HPDF_Page_SetWidth(page_, render_size_.x);
|
||||
HPDF_Page_SetHeight(page_, render_size_.y);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PDFExporter::write_stroke_to_polyline(const float4x4 &transform,
|
||||
const Span<float3> positions,
|
||||
const bool cyclic,
|
||||
const ColorGeometry4f &color,
|
||||
const float opacity,
|
||||
const std::optional<float> width)
|
||||
{
|
||||
if (width) {
|
||||
HPDF_Page_SetLineJoin(page_, HPDF_ROUND_JOIN);
|
||||
HPDF_Page_SetLineWidth(page_, std::max(*width, 1.0f));
|
||||
}
|
||||
|
||||
const float total_opacity = color.a * opacity;
|
||||
|
||||
HPDF_Page_GSave(page_);
|
||||
HPDF_ExtGState gstate = (total_opacity < 1.0f) ? HPDF_CreateExtGState(pdf_) : nullptr;
|
||||
|
||||
ColorGeometry4f srgb;
|
||||
linearrgb_to_srgb_v3_v3(srgb, color);
|
||||
if (width) {
|
||||
HPDF_Page_SetRGBFill(page_, srgb.r, srgb.g, srgb.b);
|
||||
HPDF_Page_SetRGBStroke(page_, srgb.r, srgb.g, srgb.b);
|
||||
if (gstate) {
|
||||
HPDF_ExtGState_SetAlphaFill(gstate, std::clamp(opacity, 0.0f, 1.0f));
|
||||
HPDF_ExtGState_SetAlphaStroke(gstate, std::clamp(opacity, 0.0f, 1.0f));
|
||||
}
|
||||
}
|
||||
else {
|
||||
HPDF_Page_SetRGBFill(page_, srgb.r, srgb.g, srgb.b);
|
||||
if (gstate) {
|
||||
HPDF_ExtGState_SetAlphaFill(gstate, std::clamp(opacity, 0.0f, 1.0f));
|
||||
}
|
||||
}
|
||||
if (gstate) {
|
||||
HPDF_Page_SetExtGState(page_, gstate);
|
||||
}
|
||||
|
||||
for (const int i : positions.index_range()) {
|
||||
const float2 screen_co = this->project_to_screen(transform, positions[i]);
|
||||
if (i == 0) {
|
||||
HPDF_Page_MoveTo(page_, screen_co.x, screen_co.y);
|
||||
}
|
||||
else {
|
||||
HPDF_Page_LineTo(page_, screen_co.x, screen_co.y);
|
||||
}
|
||||
}
|
||||
if (cyclic) {
|
||||
HPDF_Page_ClosePath(page_);
|
||||
}
|
||||
|
||||
if (width) {
|
||||
HPDF_Page_Stroke(page_);
|
||||
}
|
||||
else {
|
||||
HPDF_Page_Fill(page_);
|
||||
}
|
||||
|
||||
HPDF_Page_GRestore(page_);
|
||||
}
|
||||
|
||||
bool PDFExporter::write_to_file(StringRefNull filepath)
|
||||
{
|
||||
/* Support unicode character paths on Windows. */
|
||||
HPDF_STATUS result = 0;
|
||||
|
||||
/* TODO: It looks `libharu` does not support unicode. */
|
||||
#if 0 /* `ifdef WIN32` */
|
||||
wchar_t *filepath_16 = alloc_utf16_from_8(filepath.c_str(), 0);
|
||||
std::wstring wstr(filepath_16);
|
||||
result = HPDF_SaveToFile(pdf_, wstr.c_str());
|
||||
free(filepath_16);
|
||||
#else
|
||||
result = HPDF_SaveToFile(pdf_, filepath.c_str());
|
||||
#endif
|
||||
|
||||
return (result == 0) ? true : false;
|
||||
}
|
||||
|
||||
bool export_pdf(const IOContext &context,
|
||||
const ExportParams ¶ms,
|
||||
Scene &scene,
|
||||
StringRefNull filepath)
|
||||
{
|
||||
PDFExporter exporter(context, params);
|
||||
return exporter.export_scene(scene, filepath);
|
||||
}
|
||||
|
||||
} // namespace blender::io::grease_pencil
|
||||
@@ -0,0 +1,388 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_curves.hh"
|
||||
#include "BKE_material.h"
|
||||
#include "BLI_color.hh"
|
||||
#include "BLI_math_matrix.hh"
|
||||
#include "BLI_offset_indices.hh"
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_task.hh"
|
||||
#include "BLI_vector.hh"
|
||||
#include "BLI_virtual_array.hh"
|
||||
|
||||
#include "BKE_grease_pencil.hh"
|
||||
|
||||
#include "DNA_material_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
#include "DEG_depsgraph_query.hh"
|
||||
#include "DNA_view3d_types.h"
|
||||
|
||||
#include "GEO_resample_curves.hh"
|
||||
|
||||
#include "ED_grease_pencil.hh"
|
||||
#include "ED_view3d.hh"
|
||||
|
||||
#include "grease_pencil_io_intern.hh"
|
||||
|
||||
#include <fmt/core.h>
|
||||
#include <fmt/format.h>
|
||||
#include <numeric>
|
||||
#include <optional>
|
||||
#include <pugixml.hpp>
|
||||
|
||||
#ifdef WIN32
|
||||
# include "utfconv.hh"
|
||||
#endif
|
||||
|
||||
/** \file
|
||||
* \ingroup bgrease_pencil
|
||||
*/
|
||||
|
||||
namespace blender::io::grease_pencil {
|
||||
|
||||
constexpr const char *svg_exporter_name = "SVG Export for Grease Pencil";
|
||||
constexpr const char *svg_exporter_version = "v2.0";
|
||||
|
||||
static std::string rgb_to_hexstr(const float color[3])
|
||||
{
|
||||
uint8_t r = color[0] * 255.0f;
|
||||
uint8_t g = color[1] * 255.0f;
|
||||
uint8_t b = color[2] * 255.0f;
|
||||
return fmt::format("#{:02X}{:02X}{:02X}", r, g, b);
|
||||
}
|
||||
|
||||
static void write_stroke_color_attribute(pugi::xml_node node,
|
||||
const ColorGeometry4f &stroke_color,
|
||||
const float stroke_opacity,
|
||||
const bool round_cap)
|
||||
{
|
||||
ColorGeometry4f color;
|
||||
linearrgb_to_srgb_v3_v3(color, stroke_color);
|
||||
std::string stroke_hex = rgb_to_hexstr(color);
|
||||
|
||||
node.append_attribute("stroke").set_value(stroke_hex.c_str());
|
||||
node.append_attribute("stroke-opacity").set_value(stroke_color.a * stroke_opacity);
|
||||
|
||||
node.append_attribute("fill").set_value("none");
|
||||
node.append_attribute("stroke-linecap").set_value(round_cap ? "round" : "square");
|
||||
}
|
||||
|
||||
static void write_fill_color_attribute(pugi::xml_node node,
|
||||
const ColorGeometry4f &fill_color,
|
||||
const float layer_opacity)
|
||||
{
|
||||
ColorGeometry4f color;
|
||||
linearrgb_to_srgb_v3_v3(color, fill_color);
|
||||
std::string stroke_hex = rgb_to_hexstr(color);
|
||||
|
||||
node.append_attribute("fill").set_value(stroke_hex.c_str());
|
||||
node.append_attribute("stroke").set_value("none");
|
||||
node.append_attribute("fill-opacity").set_value(fill_color.a * layer_opacity);
|
||||
}
|
||||
|
||||
static void write_rect(pugi::xml_node node,
|
||||
const float x,
|
||||
const float y,
|
||||
const float width,
|
||||
const float height,
|
||||
const float thickness,
|
||||
const std::string &hexcolor)
|
||||
{
|
||||
pugi::xml_node rect_node = node.append_child("rect");
|
||||
rect_node.append_attribute("x").set_value(x);
|
||||
rect_node.append_attribute("y").set_value(y);
|
||||
rect_node.append_attribute("width").set_value(width);
|
||||
rect_node.append_attribute("height").set_value(height);
|
||||
rect_node.append_attribute("fill").set_value("none");
|
||||
if (thickness > 0.0f) {
|
||||
rect_node.append_attribute("stroke").set_value(hexcolor.c_str());
|
||||
rect_node.append_attribute("stroke-width").set_value(thickness);
|
||||
}
|
||||
}
|
||||
|
||||
class SVGExporter : public GreasePencilExporter {
|
||||
public:
|
||||
using GreasePencilExporter::GreasePencilExporter;
|
||||
|
||||
pugi::xml_document main_doc_;
|
||||
|
||||
bool export_scene(Scene &scene, StringRefNull filepath);
|
||||
void export_grease_pencil_objects(pugi::xml_node node, int frame_number);
|
||||
void export_grease_pencil_layer(pugi::xml_node node,
|
||||
const Object &object,
|
||||
const bke::greasepencil::Layer &layer,
|
||||
const bke::greasepencil::Drawing &drawing);
|
||||
|
||||
void write_document_header();
|
||||
pugi::xml_node write_main_node();
|
||||
pugi::xml_node write_polygon(pugi::xml_node node,
|
||||
const float4x4 &transform,
|
||||
Span<float3> positions);
|
||||
pugi::xml_node write_polyline(pugi::xml_node node,
|
||||
const float4x4 &transform,
|
||||
Span<float3> positions,
|
||||
bool cyclic,
|
||||
std::optional<float> width);
|
||||
pugi::xml_node write_path(pugi::xml_node node,
|
||||
const float4x4 &transform,
|
||||
Span<float3> positions,
|
||||
bool cyclic);
|
||||
|
||||
bool write_to_file(StringRefNull filepath);
|
||||
};
|
||||
|
||||
bool SVGExporter::export_scene(Scene &scene, StringRefNull filepath)
|
||||
{
|
||||
const int frame_number = scene.r.cfra;
|
||||
|
||||
this->prepare_camera_params(scene, frame_number, false);
|
||||
|
||||
this->write_document_header();
|
||||
pugi::xml_node main_node = this->write_main_node();
|
||||
this->export_grease_pencil_objects(main_node, frame_number);
|
||||
|
||||
return this->write_to_file(filepath);
|
||||
}
|
||||
|
||||
void SVGExporter::export_grease_pencil_objects(pugi::xml_node node, const int frame_number)
|
||||
{
|
||||
using bke::greasepencil::Drawing;
|
||||
|
||||
const bool is_clipping = is_camera_ && params_.use_clip_camera;
|
||||
|
||||
Vector<ObjectInfo> objects = retrieve_objects();
|
||||
|
||||
for (const ObjectInfo &info : objects) {
|
||||
const Object *ob = info.object;
|
||||
|
||||
/* Camera clipping. */
|
||||
if (is_clipping) {
|
||||
pugi::xml_node clip_node = node.append_child("clipPath");
|
||||
clip_node.append_attribute("id").set_value(
|
||||
("clip-path" + std::to_string(frame_number)).c_str());
|
||||
|
||||
write_rect(clip_node, 0, 0, render_size_.x, render_size_.y, 0.0f, "#000000");
|
||||
}
|
||||
|
||||
pugi::xml_node frame_node = node.append_child("g");
|
||||
std::string frametxt = "blender_frame_" + std::to_string(frame_number);
|
||||
frame_node.append_attribute("id").set_value(frametxt.c_str());
|
||||
|
||||
/* Clip area. */
|
||||
if (is_clipping) {
|
||||
frame_node.append_attribute("clip-path")
|
||||
.set_value(("url(#clip-path" + std::to_string(frame_number) + ")").c_str());
|
||||
}
|
||||
|
||||
pugi::xml_node ob_node = frame_node.append_child("g");
|
||||
|
||||
char obtxt[96];
|
||||
SNPRINTF(obtxt, "blender_object_%s", ob->id.name + 2);
|
||||
ob_node.append_attribute("id").set_value(obtxt);
|
||||
|
||||
/* Use evaluated version to get strokes with modifiers. */
|
||||
Object *ob_eval = DEG_get_evaluated_object(context_.depsgraph, const_cast<Object *>(ob));
|
||||
BLI_assert(ob_eval->type == OB_GREASE_PENCIL);
|
||||
const GreasePencil *grease_pencil_eval = static_cast<const GreasePencil *>(ob_eval->data);
|
||||
|
||||
for (const bke::greasepencil::Layer *layer : grease_pencil_eval->layers()) {
|
||||
if (!layer->is_visible()) {
|
||||
return;
|
||||
}
|
||||
const Drawing *drawing = grease_pencil_eval->get_drawing_at(*layer, frame_number);
|
||||
if (drawing == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Layer node. */
|
||||
const std::string txt = "Layer: " + layer->name();
|
||||
node.append_child(pugi::node_comment).set_value(txt.c_str());
|
||||
|
||||
pugi::xml_node layer_node = node.append_child("g");
|
||||
layer_node.append_attribute("id").set_value(layer->name().c_str());
|
||||
|
||||
export_grease_pencil_layer(layer_node, *ob_eval, *layer, *drawing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SVGExporter::export_grease_pencil_layer(pugi::xml_node layer_node,
|
||||
const Object &object,
|
||||
const bke::greasepencil::Layer &layer,
|
||||
const bke::greasepencil::Drawing &drawing)
|
||||
{
|
||||
using bke::greasepencil::Drawing;
|
||||
|
||||
const float4x4 layer_to_world = layer.to_world_space(object);
|
||||
const float4x4 viewmat = float4x4(context_.rv3d->viewmat);
|
||||
/* SVG has inverted Y axis. */
|
||||
const float4x4 svg_coords = math::from_scale<float4x4>(float3(1, -1, 1));
|
||||
const float4x4 layer_to_view = svg_coords * viewmat * layer_to_world;
|
||||
|
||||
auto write_stroke = [&](const Span<float3> positions,
|
||||
const bool cyclic,
|
||||
const ColorGeometry4f &color,
|
||||
const float opacity,
|
||||
const std::optional<float> width,
|
||||
const bool round_cap,
|
||||
const bool is_outline) {
|
||||
if (is_outline) {
|
||||
pugi::xml_node element_node = write_path(layer_node, layer_to_view, positions, cyclic);
|
||||
write_fill_color_attribute(element_node, color, opacity);
|
||||
}
|
||||
else {
|
||||
/* Fill is always exported as polygon because the stroke of the fill is done
|
||||
* in a different SVG command. */
|
||||
pugi::xml_node element_node = write_polyline(
|
||||
layer_node, layer_to_view, positions, cyclic, width);
|
||||
|
||||
if (width) {
|
||||
write_stroke_color_attribute(element_node, color, opacity, round_cap);
|
||||
}
|
||||
else {
|
||||
write_fill_color_attribute(element_node, color, opacity);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
foreach_stroke_in_layer(object, layer, drawing, write_stroke);
|
||||
}
|
||||
|
||||
void SVGExporter::write_document_header()
|
||||
{
|
||||
/* Add a custom document declaration node. */
|
||||
pugi::xml_node decl = main_doc_.prepend_child(pugi::node_declaration);
|
||||
decl.append_attribute("version") = "1.0";
|
||||
decl.append_attribute("encoding") = "UTF-8";
|
||||
|
||||
pugi::xml_node comment = main_doc_.append_child(pugi::node_comment);
|
||||
std::string txt = std::string(" Generator: Blender, ") + svg_exporter_name + " - " +
|
||||
svg_exporter_version + " ";
|
||||
comment.set_value(txt.c_str());
|
||||
|
||||
pugi::xml_node doctype = main_doc_.append_child(pugi::node_doctype);
|
||||
doctype.set_value(
|
||||
"svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
|
||||
"\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\"");
|
||||
}
|
||||
|
||||
pugi::xml_node SVGExporter::write_main_node()
|
||||
{
|
||||
pugi::xml_node main_node = main_doc_.append_child("svg");
|
||||
main_node.append_attribute("version").set_value("1.0");
|
||||
main_node.append_attribute("x").set_value("0px");
|
||||
main_node.append_attribute("y").set_value("0px");
|
||||
main_node.append_attribute("xmlns").set_value("http://www.w3.org/2000/svg");
|
||||
|
||||
std::string width = std::to_string(render_size_.x);
|
||||
std::string height = std::to_string(render_size_.y);
|
||||
|
||||
main_node.append_attribute("width").set_value((width + "px").c_str());
|
||||
main_node.append_attribute("height").set_value((height + "px").c_str());
|
||||
std::string viewbox = "0 0 " + width + " " + height;
|
||||
main_node.append_attribute("viewBox").set_value(viewbox.c_str());
|
||||
|
||||
return main_node;
|
||||
}
|
||||
|
||||
pugi::xml_node SVGExporter::write_polygon(pugi::xml_node node,
|
||||
const float4x4 &transform,
|
||||
const Span<float3> positions)
|
||||
{
|
||||
pugi::xml_node element_node = node.append_child("polygon");
|
||||
|
||||
std::string txt;
|
||||
for (const int i : positions.index_range()) {
|
||||
if (i > 0) {
|
||||
txt.append(" ");
|
||||
}
|
||||
const float2 screen_co = this->project_to_screen(transform, positions[i]);
|
||||
txt.append(std::to_string(screen_co.x) + "," + std::to_string(screen_co.y));
|
||||
}
|
||||
|
||||
element_node.append_attribute("points").set_value(txt.c_str());
|
||||
|
||||
return element_node;
|
||||
}
|
||||
|
||||
pugi::xml_node SVGExporter::write_polyline(pugi::xml_node node,
|
||||
const float4x4 &transform,
|
||||
const Span<float3> positions,
|
||||
const bool cyclic,
|
||||
const std::optional<float> width)
|
||||
{
|
||||
pugi::xml_node element_node = node.append_child(cyclic ? "polygon" : "polyline");
|
||||
|
||||
if (width) {
|
||||
element_node.append_attribute("stroke-width").set_value(*width);
|
||||
}
|
||||
|
||||
std::string txt;
|
||||
for (const int i : positions.index_range()) {
|
||||
if (i > 0) {
|
||||
txt.append(" ");
|
||||
}
|
||||
const float2 screen_co = this->project_to_screen(transform, positions[i]);
|
||||
txt.append(std::to_string(screen_co.x) + "," + std::to_string(screen_co.y));
|
||||
}
|
||||
|
||||
element_node.append_attribute("points").set_value(txt.c_str());
|
||||
|
||||
return element_node;
|
||||
}
|
||||
|
||||
pugi::xml_node SVGExporter::write_path(pugi::xml_node node,
|
||||
const float4x4 &transform,
|
||||
const Span<float3> positions,
|
||||
const bool cyclic)
|
||||
{
|
||||
pugi::xml_node element_node = node.append_child("path");
|
||||
|
||||
std::string txt = "M";
|
||||
for (const int i : positions.index_range()) {
|
||||
if (i > 0) {
|
||||
txt.append("L");
|
||||
}
|
||||
const float2 screen_co = this->project_to_screen(transform, positions[i]);
|
||||
txt.append(std::to_string(screen_co.x) + "," + std::to_string(screen_co.y));
|
||||
}
|
||||
/* Close patch (cyclic). */
|
||||
if (cyclic) {
|
||||
txt.append("z");
|
||||
}
|
||||
|
||||
element_node.append_attribute("d").set_value(txt.c_str());
|
||||
|
||||
return element_node;
|
||||
}
|
||||
|
||||
bool SVGExporter::write_to_file(StringRefNull filepath)
|
||||
{
|
||||
bool result = true;
|
||||
/* Support unicode character paths on Windows. */
|
||||
#ifdef WIN32
|
||||
wchar_t *filepath_16 = alloc_utf16_from_8(filepath.c_str(), 0);
|
||||
std::wstring wstr(filepath_16);
|
||||
result = main_doc_.save_file(wstr.c_str());
|
||||
free(filepath_16);
|
||||
#else
|
||||
result = main_doc_.save_file(filepath.c_str());
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool export_svg(const IOContext &context,
|
||||
const ExportParams ¶ms,
|
||||
Scene &scene,
|
||||
StringRefNull filepath)
|
||||
{
|
||||
SVGExporter exporter(context, params);
|
||||
return exporter.export_scene(scene, filepath);
|
||||
}
|
||||
|
||||
} // namespace blender::io::grease_pencil
|
||||
@@ -0,0 +1,366 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_main.hh"
|
||||
#include "BLI_assert.h"
|
||||
#include "BLI_bounds.hh"
|
||||
#include "BLI_color.hh"
|
||||
#include "BLI_math_color.h"
|
||||
#include "BLI_math_euler_types.hh"
|
||||
#include "BLI_math_matrix.hh"
|
||||
#include "BLI_math_vector.hh"
|
||||
#include "BLI_offset_indices.hh"
|
||||
#include "BLI_path_util.h"
|
||||
|
||||
#include "BKE_curves.hh"
|
||||
#include "BKE_grease_pencil.hh"
|
||||
#include "BKE_report.hh"
|
||||
|
||||
#include "BLI_string.h"
|
||||
#include "DNA_grease_pencil_types.h"
|
||||
#include "DNA_space_types.h"
|
||||
#include "DNA_windowmanager_types.h"
|
||||
|
||||
#include "GEO_resample_curves.hh"
|
||||
|
||||
#include "ED_grease_pencil.hh"
|
||||
|
||||
#include "grease_pencil_io_intern.hh"
|
||||
|
||||
#include "nanosvg.h"
|
||||
|
||||
#include <fmt/core.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
/** \file
|
||||
* \ingroup bgrease_pencil
|
||||
*/
|
||||
|
||||
using blender::bke::greasepencil::Drawing;
|
||||
using blender::bke::greasepencil::Layer;
|
||||
using blender::bke::greasepencil::TreeNode;
|
||||
|
||||
namespace blender::io::grease_pencil {
|
||||
|
||||
class SVGImporter : public GreasePencilImporter {
|
||||
public:
|
||||
using GreasePencilImporter::GreasePencilImporter;
|
||||
|
||||
bool read(StringRefNull filepath);
|
||||
};
|
||||
|
||||
static std::string get_layer_id(const NSVGshape &shape, const int prefix)
|
||||
{
|
||||
return (shape.id_parent[0] == '\0') ? fmt::format("Layer_{:03d}", prefix) :
|
||||
fmt::format("{:s}", shape.id_parent);
|
||||
}
|
||||
|
||||
/* Unpack internal NanoSVG color. */
|
||||
static ColorGeometry4f unpack_nano_color(const uint pack)
|
||||
{
|
||||
const uchar4 rgb_u = {uint8_t(((pack) >> 0) & 0xFF),
|
||||
uint8_t(((pack) >> 8) & 0xFF),
|
||||
uint8_t(((pack) >> 16) & 0xFF),
|
||||
uint8_t(((pack) >> 24) & 0xFF)};
|
||||
const float4 rgb_f = {float(rgb_u[0]) / 255.0f,
|
||||
float(rgb_u[1]) / 255.0f,
|
||||
float(rgb_u[2]) / 255.0f,
|
||||
float(rgb_u[3]) / 255.0f};
|
||||
|
||||
ColorGeometry4f color;
|
||||
srgb_to_linearrgb_v4(color, rgb_f);
|
||||
return color;
|
||||
}
|
||||
|
||||
/* TODO Gradients are not yet supported (will output magenta placeholder color).
|
||||
* This is because gradients for fill materials in particular can only be defined by materials.
|
||||
* Since each path can have a unique gradient it potentially requires a material per curve. Stroke
|
||||
* gradients could be baked into vertex colors. */
|
||||
static ColorGeometry4f convert_svg_color(const NSVGpaint &svg_paint)
|
||||
{
|
||||
switch (svg_paint.type) {
|
||||
case NSVG_PAINT_UNDEF:
|
||||
return ColorGeometry4f(1, 0, 1, 1);
|
||||
case NSVG_PAINT_NONE:
|
||||
return ColorGeometry4f(0, 0, 0, 1);
|
||||
case NSVG_PAINT_COLOR:
|
||||
return unpack_nano_color(svg_paint.color);
|
||||
case NSVG_PAINT_LINEAR_GRADIENT:
|
||||
return ColorGeometry4f(0, 0, 0, 1);
|
||||
case NSVG_PAINT_RADIAL_GRADIENT:
|
||||
return ColorGeometry4f(0, 0, 0, 1);
|
||||
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
return ColorGeometry4f(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Make room for curves and points from the SVG shape.
|
||||
* Returns the index range of newly added curves. */
|
||||
static IndexRange extend_curves_geometry(bke::CurvesGeometry &curves, const NSVGshape &shape)
|
||||
{
|
||||
const int old_curves_num = curves.curves_num();
|
||||
const int old_points_num = curves.points_num();
|
||||
const Span<int> old_offsets = curves.offsets();
|
||||
|
||||
/* Count curves and points. */
|
||||
Vector<int> new_curve_offsets;
|
||||
for (NSVGpath *path = shape.paths; path; path = path->next) {
|
||||
if (path->npts == 0) {
|
||||
continue;
|
||||
}
|
||||
BLI_assert(path->npts >= 1 && path->npts == int(path->npts / 3) * 3 + 1);
|
||||
/* nanosvg converts everything to bezier curves, points come in triplets. Round up to the next
|
||||
* full integer, since there is one point without handles (3*n+1 points in total). */
|
||||
const int point_num = (path->npts + 2) / 3;
|
||||
new_curve_offsets.append(point_num);
|
||||
}
|
||||
if (new_curve_offsets.is_empty()) {
|
||||
return {};
|
||||
}
|
||||
new_curve_offsets.append(0);
|
||||
const OffsetIndices new_points_by_curve = offset_indices::accumulate_counts_to_offsets(
|
||||
new_curve_offsets, old_points_num);
|
||||
|
||||
const IndexRange new_curves_range = {old_curves_num, new_points_by_curve.size()};
|
||||
const int curves_num = new_curves_range.one_after_last();
|
||||
const int points_num = new_points_by_curve.total_size();
|
||||
|
||||
Array<int> new_offsets(curves_num + 1);
|
||||
if (old_curves_num > 0) {
|
||||
new_offsets.as_mutable_span().slice(0, old_curves_num).copy_from(old_offsets.drop_back(1));
|
||||
}
|
||||
new_offsets.as_mutable_span()
|
||||
.slice(old_curves_num, new_curve_offsets.size())
|
||||
.copy_from(new_curve_offsets);
|
||||
|
||||
curves.resize(points_num, curves_num);
|
||||
curves.offsets_for_write().copy_from(new_offsets);
|
||||
|
||||
curves.tag_topology_changed();
|
||||
|
||||
return new_curves_range;
|
||||
}
|
||||
|
||||
static void shape_attributes_to_curves(bke::CurvesGeometry &curves,
|
||||
const NSVGshape &shape,
|
||||
const IndexRange curves_range,
|
||||
const float4x4 &transform,
|
||||
const int material_index)
|
||||
{
|
||||
/* Path width is twice the radius. */
|
||||
const float path_width_scale = 0.5f * math::average(math::to_scale(transform));
|
||||
const OffsetIndices points_by_curve = curves.points_by_curve();
|
||||
|
||||
/* nanosvg converts everything to Bezier curves. */
|
||||
curves.curve_types_for_write().slice(curves_range).fill(CURVE_TYPE_BEZIER);
|
||||
curves.update_curve_types();
|
||||
|
||||
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
|
||||
bke::SpanAttributeWriter<int> materials = attributes.lookup_or_add_for_write_span<int>(
|
||||
"material_index", bke::AttrDomain::Curve);
|
||||
bke::SpanAttributeWriter fill_colors = attributes.lookup_or_add_for_write_span<ColorGeometry4f>(
|
||||
"fill_color", bke::AttrDomain::Curve);
|
||||
MutableSpan<bool> cyclic = curves.cyclic_for_write();
|
||||
bke::SpanAttributeWriter<float> fill_opacities = attributes.lookup_or_add_for_write_span<float>(
|
||||
"fill_opacity", bke::AttrDomain::Curve);
|
||||
|
||||
MutableSpan<float3> positions = curves.positions_for_write();
|
||||
MutableSpan<float3> handle_positions_left = curves.handle_positions_left_for_write();
|
||||
MutableSpan<float3> handle_positions_right = curves.handle_positions_right_for_write();
|
||||
MutableSpan<int8_t> handle_types_left = curves.handle_types_left_for_write();
|
||||
MutableSpan<int8_t> handle_types_right = curves.handle_types_right_for_write();
|
||||
bke::SpanAttributeWriter<float> radii = attributes.lookup_or_add_for_write_span<float>(
|
||||
"radius", bke::AttrDomain::Point);
|
||||
bke::SpanAttributeWriter<ColorGeometry4f> vertex_colors =
|
||||
attributes.lookup_or_add_for_write_span<ColorGeometry4f>("vertex_color",
|
||||
bke::AttrDomain::Point);
|
||||
bke::SpanAttributeWriter<float> point_opacities = attributes.lookup_or_add_for_write_span<float>(
|
||||
"opacity", bke::AttrDomain::Point);
|
||||
|
||||
materials.span.slice(curves_range).fill(material_index);
|
||||
const ColorGeometry4f shape_color = convert_svg_color(shape.fill);
|
||||
fill_colors.span.slice(curves_range).fill(shape_color);
|
||||
fill_opacities.span.slice(curves_range).fill(shape_color.a);
|
||||
|
||||
int curve_index = curves_range.start();
|
||||
for (NSVGpath *path = shape.paths; path; path = path->next) {
|
||||
if (path->npts == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cyclic[curve_index] = bool(path->closed);
|
||||
|
||||
/* 2D vectors in triplets: [control point, left handle, right handle]. */
|
||||
const Span<float2> svg_path_data = Span<float>(path->pts, 2 * path->npts).cast<float2>();
|
||||
|
||||
const IndexRange points = points_by_curve[curve_index];
|
||||
for (const int i : points.index_range()) {
|
||||
const int point_index = points[i];
|
||||
const float2 pos_center = svg_path_data[i * 3];
|
||||
const float2 pos_handle_left = (i > 0) ? svg_path_data[i * 3 - 1] : pos_center;
|
||||
const float2 pos_handle_right = (i < points.size() - 1) ? svg_path_data[i * 3 + 1] :
|
||||
pos_center;
|
||||
positions[point_index] = math::transform_point(transform, float3(pos_center, 0.0f));
|
||||
handle_positions_left[point_index] = math::transform_point(transform,
|
||||
float3(pos_handle_left, 0.0f));
|
||||
handle_positions_right[point_index] = math::transform_point(transform,
|
||||
float3(pos_handle_right, 0.0f));
|
||||
handle_types_left[point_index] = BEZIER_HANDLE_FREE;
|
||||
handle_types_right[point_index] = BEZIER_HANDLE_FREE;
|
||||
|
||||
radii.span[point_index] = shape.strokeWidth * path_width_scale;
|
||||
|
||||
const ColorGeometry4f point_color = convert_svg_color(shape.stroke);
|
||||
vertex_colors.span[point_index] = point_color;
|
||||
point_opacities.span[point_index] = point_color.a;
|
||||
}
|
||||
|
||||
++curve_index;
|
||||
}
|
||||
|
||||
materials.finish();
|
||||
fill_colors.finish();
|
||||
fill_opacities.finish();
|
||||
radii.finish();
|
||||
vertex_colors.finish();
|
||||
point_opacities.finish();
|
||||
curves.tag_positions_changed();
|
||||
curves.tag_radii_changed();
|
||||
}
|
||||
|
||||
static void shift_to_bounds_center(GreasePencil &grease_pencil)
|
||||
{
|
||||
const std::optional<Bounds<float3>> bounds = [&]() {
|
||||
std::optional<Bounds<float3>> bounds;
|
||||
for (GreasePencilDrawingBase *drawing_base : grease_pencil.drawings()) {
|
||||
if (drawing_base->type != GP_DRAWING) {
|
||||
continue;
|
||||
}
|
||||
Drawing &drawing = reinterpret_cast<GreasePencilDrawing *>(drawing_base)->wrap();
|
||||
bounds = bounds::merge(bounds, drawing.strokes().bounds_min_max());
|
||||
}
|
||||
return bounds;
|
||||
}();
|
||||
if (!bounds) {
|
||||
return;
|
||||
}
|
||||
const float3 offset = -bounds->center();
|
||||
|
||||
for (GreasePencilDrawingBase *drawing_base : grease_pencil.drawings()) {
|
||||
if (drawing_base->type != GP_DRAWING) {
|
||||
continue;
|
||||
}
|
||||
Drawing &drawing = reinterpret_cast<GreasePencilDrawing *>(drawing_base)->wrap();
|
||||
drawing.strokes_for_write().translate(offset);
|
||||
drawing.tag_positions_changed();
|
||||
}
|
||||
}
|
||||
|
||||
bool SVGImporter::read(StringRefNull filepath)
|
||||
{
|
||||
/* Fixed SVG unit for scaling. */
|
||||
constexpr const char *svg_units = "mm";
|
||||
constexpr float svg_dpi = 96.0f;
|
||||
|
||||
char abs_filepath[FILE_MAX];
|
||||
BLI_strncpy(abs_filepath, filepath.c_str(), sizeof(abs_filepath));
|
||||
BLI_path_abs(abs_filepath, BKE_main_blendfile_path_from_global());
|
||||
|
||||
NSVGimage *svg_data = nullptr;
|
||||
svg_data = nsvgParseFromFile(abs_filepath, svg_units, svg_dpi);
|
||||
if (svg_data == nullptr) {
|
||||
BKE_report(context_.reports, RPT_ERROR, "Could not open SVG");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Create grease pencil object. */
|
||||
char filename[FILE_MAX];
|
||||
BLI_path_split_file_part(abs_filepath, filename, ARRAY_SIZE(filename));
|
||||
object_ = create_object(filename);
|
||||
if (object_ == nullptr) {
|
||||
BKE_report(context_.reports, RPT_ERROR, "Unable to create new object");
|
||||
nsvgDelete(svg_data);
|
||||
return false;
|
||||
}
|
||||
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object_->data);
|
||||
|
||||
const float scene_unit_scale = (context_.scene->unit.system != USER_UNIT_NONE &&
|
||||
params_.use_scene_unit) ?
|
||||
context_.scene->unit.scale_length :
|
||||
1.0f;
|
||||
/* Overall scale for SVG coordinates in millimeters. */
|
||||
const float svg_scale = 0.001f * scene_unit_scale * params_.scale;
|
||||
/* Grease pencil is rotated 90 degrees in X axis by default. */
|
||||
const float4x4 transform = math::scale(math::from_rotation<float4x4>(math::EulerXYZ(-90, 0, 0)),
|
||||
float3(svg_scale));
|
||||
|
||||
/* Loop all shapes. */
|
||||
std::string prv_id = "*";
|
||||
int prefix = 0;
|
||||
for (NSVGshape *shape = svg_data->shapes; shape; shape = shape->next) {
|
||||
std::string layer_id = get_layer_id(*shape, prefix);
|
||||
if (prv_id != layer_id) {
|
||||
prefix++;
|
||||
layer_id = get_layer_id(*shape, prefix);
|
||||
prv_id = layer_id;
|
||||
}
|
||||
|
||||
/* Check if the layer exist and create if needed. */
|
||||
Layer &layer = [&]() -> Layer & {
|
||||
TreeNode *layer_node = grease_pencil.find_node_by_name(layer_id);
|
||||
if (layer_node && layer_node->is_layer()) {
|
||||
return layer_node->as_layer();
|
||||
}
|
||||
|
||||
Layer &layer = grease_pencil.add_layer(layer_id);
|
||||
layer.as_node().flag |= GP_LAYER_TREE_NODE_USE_LIGHTS;
|
||||
return layer;
|
||||
}();
|
||||
|
||||
/* Check frame. */
|
||||
Drawing *drawing = grease_pencil.get_drawing_at(layer, params_.frame_number);
|
||||
if (drawing == nullptr) {
|
||||
drawing = grease_pencil.insert_frame(layer, params_.frame_number);
|
||||
if (!drawing) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Create materials. */
|
||||
const bool is_fill = bool(shape->fill.type);
|
||||
const bool is_stroke = bool(shape->stroke.type) || !is_fill;
|
||||
const StringRefNull mat_name = (is_stroke ? (is_fill ? "Both" : "Stroke") : "Fill");
|
||||
const int material_index = create_material(mat_name, is_stroke, is_fill);
|
||||
|
||||
bke::CurvesGeometry &curves = drawing->strokes_for_write();
|
||||
const IndexRange new_curves_range = extend_curves_geometry(curves, *shape);
|
||||
if (new_curves_range.is_empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
shape_attributes_to_curves(curves, *shape, new_curves_range, transform, material_index);
|
||||
drawing->strokes_for_write() = std::move(curves);
|
||||
}
|
||||
|
||||
/* Free SVG memory. */
|
||||
nsvgDelete(svg_data);
|
||||
|
||||
/* Calculate bounding box and move all points to new origin center. */
|
||||
if (params_.recenter_bounds) {
|
||||
shift_to_bounds_center(grease_pencil);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool import_svg(const IOContext &context, const ImportParams ¶ms, StringRefNull filepath)
|
||||
{
|
||||
SVGImporter importer(context, params);
|
||||
return importer.read(filepath);
|
||||
}
|
||||
|
||||
} // namespace blender::io::grease_pencil
|
||||
@@ -0,0 +1,103 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_color.hh"
|
||||
#include "BLI_function_ref.hh"
|
||||
#include "BLI_math_matrix_types.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "DNA_vec_types.h"
|
||||
|
||||
#include "grease_pencil_io.hh"
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup bgrease_pencil
|
||||
*/
|
||||
|
||||
struct Scene;
|
||||
struct Object;
|
||||
struct GreasePencil;
|
||||
struct Material;
|
||||
struct RegionView3D;
|
||||
namespace blender::bke::greasepencil {
|
||||
class Layer;
|
||||
class Drawing;
|
||||
} // namespace blender::bke::greasepencil
|
||||
|
||||
namespace blender::io::grease_pencil {
|
||||
|
||||
class GreasePencilImporter {
|
||||
protected:
|
||||
const IOContext context_;
|
||||
const ImportParams params_;
|
||||
|
||||
Object *object_ = nullptr;
|
||||
|
||||
public:
|
||||
GreasePencilImporter(const IOContext &context, const ImportParams ¶ms);
|
||||
|
||||
Object *create_object(StringRefNull name);
|
||||
int32_t create_material(StringRefNull name, bool stroke, bool fill);
|
||||
};
|
||||
|
||||
class GreasePencilExporter {
|
||||
public:
|
||||
struct ObjectInfo {
|
||||
Object *object;
|
||||
float depth;
|
||||
};
|
||||
|
||||
protected:
|
||||
const IOContext context_;
|
||||
const ExportParams params_;
|
||||
|
||||
/* Camera parameters. */
|
||||
float4x4 persmat_;
|
||||
int2 win_size_;
|
||||
int2 render_size_;
|
||||
bool is_camera_;
|
||||
float camera_ratio_;
|
||||
rctf camera_rect_;
|
||||
|
||||
float2 offset_;
|
||||
|
||||
public:
|
||||
GreasePencilExporter(const IOContext &context, const ExportParams ¶ms);
|
||||
|
||||
void prepare_camera_params(Scene &scene, int frame_number, bool force_camera_view);
|
||||
|
||||
static ColorGeometry4f compute_average_stroke_color(const Material &material,
|
||||
const Span<ColorGeometry4f> vertex_colors);
|
||||
static float compute_average_stroke_opacity(const Span<float> opacities);
|
||||
|
||||
/* Returns a value if point sizes are all equal. */
|
||||
static std::optional<float> try_get_uniform_point_width(const RegionView3D &rv3d,
|
||||
const Span<float3> world_positions,
|
||||
const Span<float> radii);
|
||||
|
||||
Vector<ObjectInfo> retrieve_objects() const;
|
||||
|
||||
using WriteStrokeFn = FunctionRef<void(const Span<float3> positions,
|
||||
bool cyclic,
|
||||
const ColorGeometry4f &color,
|
||||
float opacity,
|
||||
std::optional<float> width,
|
||||
bool round_cap,
|
||||
bool is_outline)>;
|
||||
|
||||
void foreach_stroke_in_layer(const Object &object,
|
||||
const bke::greasepencil::Layer &layer,
|
||||
const bke::greasepencil::Drawing &drawing,
|
||||
WriteStrokeFn stroke_fn);
|
||||
|
||||
float2 project_to_screen(const float4x4 &transform, const float3 &position) const;
|
||||
};
|
||||
|
||||
} // namespace blender::io::grease_pencil
|
||||
@@ -328,8 +328,8 @@ if(WITH_IO_STL)
|
||||
add_definitions(-DWITH_IO_STL)
|
||||
endif()
|
||||
|
||||
if(WITH_IO_GPENCIL)
|
||||
add_definitions(-DWITH_IO_GPENCIL)
|
||||
if(WITH_IO_GREASE_PENCIL)
|
||||
add_definitions(-DWITH_IO_GREASE_PENCIL)
|
||||
endif()
|
||||
|
||||
if(WITH_ALEMBIC)
|
||||
|
||||
@@ -263,7 +263,7 @@ static PyObject *make_builtopts_info()
|
||||
SetObjIncref(Py_False);
|
||||
#endif
|
||||
|
||||
#ifdef WITH_IO_GPENCIL
|
||||
#ifdef WITH_IO_GREASE_PENCIL
|
||||
SetObjIncref(Py_True);
|
||||
#else
|
||||
SetObjIncref(Py_False);
|
||||
|
||||
Reference in New Issue
Block a user