GPencil: New modules for Import and Export

This patch adds support to export and import grease pencil in several formats.

Inlude:

* Export SVG
* Export PDF (always from camera view)

* Import SVG

The import and export only support solid colors and not gradients or textures.

Requires libharu and pugixml.

For importing SVG, the NanoSVG lib is used, but this does not require installation (just a .h file embedded in the project  folder)

Example of PDF export: https://youtu.be/BMm0KeMJsI4

Reviewed By: #grease_pencil, HooglyBoogly

Maniphest Tasks: T83190, T79875, T83191, T83192

Differential Revision: https://developer.blender.org/D10482
This commit is contained in:
Antonio Vazquez 2021-03-24 15:14:43 +01:00 committed by Antonio Vazquez
parent ce359da5b3
commit a8a92cd15a
Notes: blender-bot 2023-02-14 18:50:04 +01:00
Referenced by commit d0d0d9d7c6, Fix broken compilation after recent GPencil commit.
Referenced by issue #88853, SVG imports poorly
Referenced by issue #83190, GPencil: Import/Export main task
Referenced by issue #83191, GPencil: Export to SVG
Referenced by issue #83192, GPencil: Export to PDF
Referenced by issue blender/blender-addons#79875, GPencil: Import SVG files
30 changed files with 6990 additions and 2 deletions

View File

@ -469,6 +469,8 @@ class TOPBAR_MT_file_import(Menu):
if bpy.app.build_options.alembic:
self.layout.operator("wm.alembic_import", text="Alembic (.abc)")
self.layout.operator("wm.gpencil_import_svg", text="SVG as Grease Pencil")
class TOPBAR_MT_file_export(Menu):
bl_idname = "TOPBAR_MT_file_export"
@ -485,6 +487,13 @@ class TOPBAR_MT_file_export(Menu):
self.layout.operator(
"wm.usd_export", text="Universal Scene Description (.usd, .usdc, .usda)")
# Pugixml lib dependency
if bpy.app.build_options.pugixml:
self.layout.operator("wm.gpencil_export_svg", text="Grease Pencil as SVG")
# Haru lib dependency
if bpy.app.build_options.haru:
self.layout.operator("wm.gpencil_export_pdf", text="Grease Pencil as PDF")
class TOPBAR_MT_file_external_data(Menu):
bl_label = "External Data"

View File

@ -27,10 +27,10 @@
extern "C" {
#endif
struct BoundBox;
struct Depsgraph;
struct Main;
struct Object;
struct RegionView3D;
struct Scene;
struct bGPDcurve;
struct bGPDframe;
@ -173,6 +173,20 @@ void BKE_gpencil_stroke_uniform_subdivide(struct bGPdata *gpd,
const uint32_t target_number,
const bool select);
void BKE_gpencil_stroke_to_view_space(struct RegionView3D *rv3d,
struct bGPDstroke *gps,
const float diff_mat[4][4]);
void BKE_gpencil_stroke_from_view_space(struct RegionView3D *rv3d,
struct bGPDstroke *gps,
const float diff_mat[4][4]);
struct bGPDstroke *BKE_gpencil_stroke_perimeter_from_view(struct RegionView3D *rv3d,
struct bGPdata *gpd,
const struct bGPDlayer *gpl,
struct bGPDstroke *gps,
const int subdivisions,
const float diff_mat[4][4]);
float BKE_gpencil_stroke_average_pressure_get(struct bGPDstroke *gps);
bool BKE_gpencil_stroke_is_pressure_constant(struct bGPDstroke *gps);
#ifdef __cplusplus
}
#endif

View File

@ -46,9 +46,11 @@
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "BLT_translation.h"
#include "BKE_context.h"
#include "BKE_deform.h"
#include "BKE_gpencil.h"
#include "BKE_gpencil_curve.h"
@ -3460,4 +3462,555 @@ void BKE_gpencil_stroke_uniform_subdivide(bGPdata *gpd,
BKE_gpencil_stroke_geometry_update(gpd, gps);
}
/**
* Stroke to view space
* Transforms a stroke to view space. This allows for manipulations in 2D but also easy conversion
* back to 3D.
* Note: also takes care of parent space transform
*/
void BKE_gpencil_stroke_to_view_space(RegionView3D *rv3d,
bGPDstroke *gps,
const float diff_mat[4][4])
{
for (int i = 0; i < gps->totpoints; i++) {
bGPDspoint *pt = &gps->points[i];
/* Point to parent space. */
mul_v3_m4v3(&pt->x, diff_mat, &pt->x);
/* point to view space */
mul_m4_v3(rv3d->viewmat, &pt->x);
}
}
/**
* Stroke from view space
* Transforms a stroke from view space back to world space. Inverse of
* BKE_gpencil_stroke_to_view_space
* Note: also takes care of parent space transform
*/
void BKE_gpencil_stroke_from_view_space(RegionView3D *rv3d,
bGPDstroke *gps,
const float diff_mat[4][4])
{
float inverse_diff_mat[4][4];
invert_m4_m4(inverse_diff_mat, diff_mat);
for (int i = 0; i < gps->totpoints; i++) {
bGPDspoint *pt = &gps->points[i];
mul_v3_m4v3(&pt->x, rv3d->viewinv, &pt->x);
mul_m4_v3(inverse_diff_mat, &pt->x);
}
}
/* ----------------------------------------------------------------------------- */
/* Stroke to perimeter */
typedef struct tPerimeterPoint {
struct tPerimeterPoint *next, *prev;
float x, y, z;
} tPerimeterPoint;
static tPerimeterPoint *new_perimeter_point(const float pt[3])
{
tPerimeterPoint *new_pt = MEM_callocN(sizeof(tPerimeterPoint), __func__);
copy_v3_v3(&new_pt->x, pt);
return new_pt;
}
static int generate_arc_from_point_to_point(ListBase *list,
tPerimeterPoint *from,
tPerimeterPoint *to,
float center_pt[3],
int subdivisions,
bool clockwise)
{
float vec_from[2];
float vec_to[2];
sub_v2_v2v2(vec_from, &from->x, center_pt);
sub_v2_v2v2(vec_to, &to->x, center_pt);
if (is_zero_v2(vec_from) || is_zero_v2(vec_to)) {
return 0;
}
float dot = dot_v2v2(vec_from, vec_to);
float det = cross_v2v2(vec_from, vec_to);
float angle = clockwise ? M_PI - atan2f(-det, -dot) : atan2f(-det, -dot) + M_PI;
/* Number of points is 2^(n+1) + 1 on half a circle (n=subdivisions)
* so we multiply by (angle / pi) to get the right amount of
* points to insert. */
int num_points = (int)(((1 << (subdivisions + 1)) - 1) * (angle / M_PI));
if (num_points > 0) {
float angle_incr = angle / (float)num_points;
float vec_p[3];
float vec_t[3];
float tmp_angle;
tPerimeterPoint *last_point;
if (clockwise) {
last_point = to;
copy_v2_v2(vec_t, vec_to);
}
else {
last_point = from;
copy_v2_v2(vec_t, vec_from);
}
for (int i = 0; i < num_points - 1; i++) {
tmp_angle = (i + 1) * angle_incr;
rotate_v2_v2fl(vec_p, vec_t, tmp_angle);
add_v2_v2(vec_p, center_pt);
vec_p[2] = center_pt[2];
tPerimeterPoint *new_point = new_perimeter_point(vec_p);
if (clockwise) {
BLI_insertlinkbefore(list, last_point, new_point);
}
else {
BLI_insertlinkafter(list, last_point, new_point);
}
last_point = new_point;
}
return num_points - 1;
}
return 0;
}
static int generate_semi_circle_from_point_to_point(ListBase *list,
tPerimeterPoint *from,
tPerimeterPoint *to,
int subdivisions)
{
int num_points = (1 << (subdivisions + 1)) + 1;
float center_pt[3];
interp_v3_v3v3(center_pt, &from->x, &to->x, 0.5f);
float vec_center[2];
sub_v2_v2v2(vec_center, &from->x, center_pt);
if (is_zero_v2(vec_center)) {
return 0;
}
float vec_p[3];
float angle_incr = M_PI / ((float)num_points - 1);
tPerimeterPoint *last_point = from;
for (int i = 1; i < num_points; i++) {
float angle = i * angle_incr;
/* Rotate vector around point to get perimeter points. */
rotate_v2_v2fl(vec_p, vec_center, angle);
add_v2_v2(vec_p, center_pt);
vec_p[2] = center_pt[2];
tPerimeterPoint *new_point = new_perimeter_point(vec_p);
BLI_insertlinkafter(list, last_point, new_point);
last_point = new_point;
}
return num_points - 1;
}
static int generate_perimeter_cap(const float point[4],
const float other_point[4],
float radius,
ListBase *list,
int subdivisions,
short cap_type)
{
float cap_vec[2];
sub_v2_v2v2(cap_vec, other_point, point);
normalize_v2(cap_vec);
float cap_nvec[2];
if (is_zero_v2(cap_vec)) {
cap_nvec[0] = 0;
cap_nvec[1] = radius;
}
else {
cap_nvec[0] = -cap_vec[1];
cap_nvec[1] = cap_vec[0];
mul_v2_fl(cap_nvec, radius);
}
float cap_nvec_inv[2];
negate_v2_v2(cap_nvec_inv, cap_nvec);
float vec_perimeter[3];
copy_v3_v3(vec_perimeter, point);
add_v2_v2(vec_perimeter, cap_nvec);
float vec_perimeter_inv[3];
copy_v3_v3(vec_perimeter_inv, point);
add_v2_v2(vec_perimeter_inv, cap_nvec_inv);
tPerimeterPoint *p_pt = new_perimeter_point(vec_perimeter);
tPerimeterPoint *p_pt_inv = new_perimeter_point(vec_perimeter_inv);
BLI_addtail(list, p_pt);
BLI_addtail(list, p_pt_inv);
int num_points = 0;
if (cap_type == GP_STROKE_CAP_ROUND) {
num_points += generate_semi_circle_from_point_to_point(list, p_pt, p_pt_inv, subdivisions);
}
return num_points + 2;
}
/**
* Calculate the perimeter (outline) of a stroke as list of tPerimeterPoint.
* \param subdivisions: Number of subdivions for the start and end caps
* \return: list of tPerimeterPoint
*/
static ListBase *gpencil_stroke_perimeter_ex(const bGPdata *gpd,
const bGPDlayer *gpl,
const bGPDstroke *gps,
int subdivisions,
int *r_num_perimeter_points)
{
/* sanity check */
if (gps->totpoints < 1) {
return NULL;
}
float defaultpixsize = 1000.0f / gpd->pixfactor;
float stroke_radius = ((gps->thickness + gpl->line_change) / defaultpixsize) / 2.0f;
ListBase *perimeter_right_side = MEM_callocN(sizeof(ListBase), __func__);
ListBase *perimeter_left_side = MEM_callocN(sizeof(ListBase), __func__);
int num_perimeter_points = 0;
bGPDspoint *first = &gps->points[0];
bGPDspoint *last = &gps->points[gps->totpoints - 1];
float first_radius = stroke_radius * first->pressure;
float last_radius = stroke_radius * last->pressure;
bGPDspoint *first_next;
bGPDspoint *last_prev;
if (gps->totpoints > 1) {
first_next = &gps->points[1];
last_prev = &gps->points[gps->totpoints - 2];
}
else {
first_next = first;
last_prev = last;
}
float first_pt[3];
float last_pt[3];
float first_next_pt[3];
float last_prev_pt[3];
copy_v3_v3(first_pt, &first->x);
copy_v3_v3(last_pt, &last->x);
copy_v3_v3(first_next_pt, &first_next->x);
copy_v3_v3(last_prev_pt, &last_prev->x);
/* edgecase if single point */
if (gps->totpoints == 1) {
first_next_pt[0] += 1.0f;
last_prev_pt[0] -= 1.0f;
}
/* generate points for start cap */
num_perimeter_points += generate_perimeter_cap(
first_pt, first_next_pt, first_radius, perimeter_right_side, subdivisions, gps->caps[0]);
/* generate perimeter points */
float curr_pt[3], next_pt[3], prev_pt[3];
float vec_next[2], vec_prev[2];
float nvec_next[2], nvec_prev[2];
float nvec_next_pt[3], nvec_prev_pt[3];
float vec_tangent[2];
float vec_miter_left[2], vec_miter_right[2];
float miter_left_pt[3], miter_right_pt[3];
for (int i = 1; i < gps->totpoints - 1; i++) {
bGPDspoint *curr = &gps->points[i];
bGPDspoint *prev = &gps->points[i - 1];
bGPDspoint *next = &gps->points[i + 1];
float radius = stroke_radius * curr->pressure;
copy_v3_v3(curr_pt, &curr->x);
copy_v3_v3(next_pt, &next->x);
copy_v3_v3(prev_pt, &prev->x);
sub_v2_v2v2(vec_prev, curr_pt, prev_pt);
sub_v2_v2v2(vec_next, next_pt, curr_pt);
float prev_length = len_v2(vec_prev);
float next_length = len_v2(vec_next);
if (normalize_v2(vec_prev) == 0.0f) {
vec_prev[0] = 1.0f;
vec_prev[1] = 0.0f;
}
if (normalize_v2(vec_next) == 0.0f) {
vec_next[0] = 1.0f;
vec_next[1] = 0.0f;
}
nvec_prev[0] = -vec_prev[1];
nvec_prev[1] = vec_prev[0];
nvec_next[0] = -vec_next[1];
nvec_next[1] = vec_next[0];
add_v2_v2v2(vec_tangent, vec_prev, vec_next);
if (normalize_v2(vec_tangent) == 0.0f) {
copy_v2_v2(vec_tangent, nvec_prev);
}
vec_miter_left[0] = -vec_tangent[1];
vec_miter_left[1] = vec_tangent[0];
/* calculate miter length */
float an1 = dot_v2v2(vec_miter_left, nvec_prev);
if (an1 == 0.0f) {
an1 = 1.0f;
}
float miter_length = radius / an1;
if (miter_length <= 0.0f) {
miter_length = 0.01f;
}
normalize_v2_length(vec_miter_left, miter_length);
copy_v2_v2(vec_miter_right, vec_miter_left);
negate_v2(vec_miter_right);
float angle = dot_v2v2(vec_next, nvec_prev);
/* add two points if angle is close to beeing straight */
if (fabsf(angle) < 0.0001f) {
normalize_v2_length(nvec_prev, radius);
normalize_v2_length(nvec_next, radius);
copy_v3_v3(nvec_prev_pt, curr_pt);
add_v2_v2(nvec_prev_pt, nvec_prev);
copy_v3_v3(nvec_next_pt, curr_pt);
negate_v2(nvec_next);
add_v2_v2(nvec_next_pt, nvec_next);
tPerimeterPoint *normal_prev = new_perimeter_point(nvec_prev_pt);
tPerimeterPoint *normal_next = new_perimeter_point(nvec_next_pt);
BLI_addtail(perimeter_left_side, normal_prev);
BLI_addtail(perimeter_right_side, normal_next);
num_perimeter_points += 2;
}
else {
/* bend to the left */
if (angle < 0.0f) {
normalize_v2_length(nvec_prev, radius);
normalize_v2_length(nvec_next, radius);
copy_v3_v3(nvec_prev_pt, curr_pt);
add_v2_v2(nvec_prev_pt, nvec_prev);
copy_v3_v3(nvec_next_pt, curr_pt);
add_v2_v2(nvec_next_pt, nvec_next);
tPerimeterPoint *normal_prev = new_perimeter_point(nvec_prev_pt);
tPerimeterPoint *normal_next = new_perimeter_point(nvec_next_pt);
BLI_addtail(perimeter_left_side, normal_prev);
BLI_addtail(perimeter_left_side, normal_next);
num_perimeter_points += 2;
num_perimeter_points += generate_arc_from_point_to_point(
perimeter_left_side, normal_prev, normal_next, curr_pt, subdivisions, true);
if (miter_length < prev_length && miter_length < next_length) {
copy_v3_v3(miter_right_pt, curr_pt);
add_v2_v2(miter_right_pt, vec_miter_right);
}
else {
copy_v3_v3(miter_right_pt, curr_pt);
negate_v2(nvec_next);
add_v2_v2(miter_right_pt, nvec_next);
}
tPerimeterPoint *miter_right = new_perimeter_point(miter_right_pt);
BLI_addtail(perimeter_right_side, miter_right);
num_perimeter_points++;
}
/* bend to the right */
else {
normalize_v2_length(nvec_prev, -radius);
normalize_v2_length(nvec_next, -radius);
copy_v3_v3(nvec_prev_pt, curr_pt);
add_v2_v2(nvec_prev_pt, nvec_prev);
copy_v3_v3(nvec_next_pt, curr_pt);
add_v2_v2(nvec_next_pt, nvec_next);
tPerimeterPoint *normal_prev = new_perimeter_point(nvec_prev_pt);
tPerimeterPoint *normal_next = new_perimeter_point(nvec_next_pt);
BLI_addtail(perimeter_right_side, normal_prev);
BLI_addtail(perimeter_right_side, normal_next);
num_perimeter_points += 2;
num_perimeter_points += generate_arc_from_point_to_point(
perimeter_right_side, normal_prev, normal_next, curr_pt, subdivisions, false);
if (miter_length < prev_length && miter_length < next_length) {
copy_v3_v3(miter_left_pt, curr_pt);
add_v2_v2(miter_left_pt, vec_miter_left);
}
else {
copy_v3_v3(miter_left_pt, curr_pt);
negate_v2(nvec_prev);
add_v2_v2(miter_left_pt, nvec_prev);
}
tPerimeterPoint *miter_left = new_perimeter_point(miter_left_pt);
BLI_addtail(perimeter_left_side, miter_left);
num_perimeter_points++;
}
}
}
/* generate points for end cap */
num_perimeter_points += generate_perimeter_cap(
last_pt, last_prev_pt, last_radius, perimeter_right_side, subdivisions, gps->caps[1]);
/* merge both sides to one list */
BLI_listbase_reverse(perimeter_right_side);
BLI_movelisttolist(perimeter_left_side,
perimeter_right_side); // perimeter_left_side contains entire list
ListBase *perimeter_list = perimeter_left_side;
/* close by creating a point close to the first (make a small gap) */
float close_pt[3];
tPerimeterPoint *close_first = (tPerimeterPoint *)perimeter_list->first;
tPerimeterPoint *close_last = (tPerimeterPoint *)perimeter_list->last;
interp_v3_v3v3(close_pt, &close_last->x, &close_first->x, 0.99f);
if (compare_v3v3(close_pt, &close_first->x, FLT_EPSILON) == false) {
tPerimeterPoint *close_p_pt = new_perimeter_point(close_pt);
BLI_addtail(perimeter_list, close_p_pt);
num_perimeter_points++;
}
/* free temp data */
BLI_freelistN(perimeter_right_side);
MEM_freeN(perimeter_right_side);
*r_num_perimeter_points = num_perimeter_points;
return perimeter_list;
}
/**
* Calculates the perimeter of a stroke projected from the view and
* returns it as a new stroke.
* \param subdivisions: Number of subdivions for the start and end caps
* \return: bGPDstroke pointer to stroke perimeter
*/
bGPDstroke *BKE_gpencil_stroke_perimeter_from_view(struct RegionView3D *rv3d,
bGPdata *gpd,
const bGPDlayer *gpl,
bGPDstroke *gps,
const int subdivisions,
const float diff_mat[4][4])
{
if (gps->totpoints == 0) {
return NULL;
}
bGPDstroke *gps_temp = BKE_gpencil_stroke_duplicate(gps, true, false);
const bool cyclic = ((gps_temp->flag & GP_STROKE_CYCLIC) != 0);
/* If Cyclic, add a new point. */
if (cyclic && (gps_temp->totpoints > 1)) {
gps_temp->totpoints++;
gps_temp->points = MEM_recallocN(gps_temp->points,
sizeof(*gps_temp->points) * gps_temp->totpoints);
bGPDspoint *pt_src = &gps_temp->points[0];
bGPDspoint *pt_dst = &gps_temp->points[gps_temp->totpoints - 1];
copy_v3_v3(&pt_dst->x, &pt_src->x);
pt_dst->pressure = pt_src->pressure;
pt_dst->strength = pt_src->strength;
pt_dst->uv_fac = 1.0f;
pt_dst->uv_rot = 0;
}
BKE_gpencil_stroke_to_view_space(rv3d, gps_temp, diff_mat);
int num_perimeter_points = 0;
ListBase *perimeter_points = gpencil_stroke_perimeter_ex(
gpd, gpl, gps_temp, subdivisions, &num_perimeter_points);
if (num_perimeter_points == 0) {
return NULL;
}
/* Create new stroke. */
bGPDstroke *perimeter_stroke = BKE_gpencil_stroke_new(gps_temp->mat_nr, num_perimeter_points, 1);
int i = 0;
LISTBASE_FOREACH_INDEX (tPerimeterPoint *, curr, perimeter_points, i) {
bGPDspoint *pt = &perimeter_stroke->points[i];
copy_v3_v3(&pt->x, &curr->x);
pt->pressure = 0.0f;
pt->strength = 1.0f;
pt->flag |= GP_SPOINT_SELECT;
}
BKE_gpencil_stroke_from_view_space(rv3d, perimeter_stroke, diff_mat);
/* Free temp data. */
BLI_freelistN(perimeter_points);
MEM_freeN(perimeter_points);
/* Triangles cache needs to be recalculated. */
BKE_gpencil_stroke_geometry_update(gpd, perimeter_stroke);
perimeter_stroke->flag |= GP_STROKE_SELECT | GP_STROKE_CYCLIC;
BKE_gpencil_free_stroke(gps_temp);
return perimeter_stroke;
}
/** Get average pressure. */
float BKE_gpencil_stroke_average_pressure_get(bGPDstroke *gps)
{
if (gps->totpoints == 1) {
return gps->points[0].pressure;
}
float tot = 0.0f;
for (int i = 0; i < gps->totpoints; i++) {
const bGPDspoint *pt = &gps->points[i];
tot += pt->pressure;
}
return tot / (float)gps->totpoints;
}
/** Check if the thickness of the stroke is constant. */
bool BKE_gpencil_stroke_is_pressure_constant(bGPDstroke *gps)
{
if (gps->totpoints == 1) {
return true;
}
const float first_pressure = gps->points[0].pressure;
for (int i = 0; i < gps->totpoints; i++) {
const bGPDspoint *pt = &gps->points[i];
if (pt->pressure != first_pressure) {
return false;
}
}
return true;
}
/** \} */

View File

@ -596,6 +596,21 @@ bool ED_gpencil_stroke_material_editable(Object *ob, const bGPDlayer *gpl, const
return true;
}
/* Check whether given stroke is visible for the current material. */
bool ED_gpencil_stroke_material_visible(Object *ob, const bGPDstroke *gps)
{
/* check if the color is editable */
MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1);
if (gp_style != NULL) {
if (gp_style->flag & GP_MATERIAL_HIDE) {
return false;
}
}
return true;
}
/* ******************************************************** */
/* Space Conversion */

View File

@ -150,6 +150,7 @@ bool ED_gpencil_stroke_can_use(const struct bContext *C, const struct bGPDstroke
bool ED_gpencil_stroke_material_editable(struct Object *ob,
const struct bGPDlayer *gpl,
const struct bGPDstroke *gps);
bool ED_gpencil_stroke_material_visible(struct Object *ob, const struct bGPDstroke *gps);
/* ----------- Grease Pencil Operators ----------------- */

View File

@ -24,6 +24,7 @@ set(INC
../../depsgraph
../../io/alembic
../../io/collada
../../io/gpencil
../../io/usd
../../makesdna
../../makesrna
@ -39,12 +40,16 @@ set(SRC
io_alembic.c
io_cache.c
io_collada.c
io_gpencil_import.c
io_gpencil_export.c
io_gpencil_utils.c
io_ops.c
io_usd.c
io_alembic.h
io_cache.h
io_collada.h
io_gpencil.h
io_ops.h
io_usd.h
)
@ -79,4 +84,14 @@ if(WITH_INTERNATIONAL)
add_definitions(-DWITH_INTERNATIONAL)
endif()
if(WITH_PUGIXML)
add_definitions(-DWITH_PUGIXML)
endif()
if(WITH_HARU)
add_definitions(-DWITH_HARU)
endif()
list(APPEND LIB bf_gpencil)
blender_add_lib(bf_editor_io "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")

View File

@ -0,0 +1,45 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
#ifndef __IO_GPENCIL_H__
#define __IO_GPENCIL_H__
/** \file
* \ingroup editor/io
*/
struct ARegion;
struct bContext;
struct View3D;
struct wmOperator;
struct wmOperatorType;
void WM_OT_gpencil_import_svg(struct wmOperatorType *ot);
#ifdef WITH_PUGIXML
void WM_OT_gpencil_export_svg(struct wmOperatorType *ot);
#endif
#ifdef WITH_HARU
void WM_OT_gpencil_export_pdf(struct wmOperatorType *ot);
#endif
struct ARegion *get_invoke_region(struct bContext *C);
struct View3D *get_invoke_view3d(struct bContext *C);
#endif /* __IO_GPENCIL_H__ */

View File

@ -0,0 +1,430 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup editor/io
*/
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "DNA_gpencil_types.h"
#include "DNA_space_types.h"
#include "BKE_gpencil.h"
#include "BKE_main.h"
#include "BKE_report.h"
#include "BKE_screen.h"
#include "BLT_translation.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "UI_interface.h"
#include "UI_resources.h"
#include "WM_api.h"
#include "WM_types.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
#include "io_gpencil.h"
#include "gpencil_io.h"
/* Definition of enum elements to export. */
/* Common props for exporting. */
static void gpencil_export_common_props_definition(wmOperatorType *ot)
{
static const EnumPropertyItem select_items[] = {
{GP_EXPORT_ACTIVE, "ACTIVE", 0, "Active", "Include only the active object"},
{GP_EXPORT_SELECTED, "SELECTED", 0, "Selected", "Include selected objects"},
{GP_EXPORT_VISIBLE, "VISIBLE", 0, "Visible", "Include all visible objects"},
{0, NULL, 0, NULL, NULL},
};
RNA_def_boolean(ot->srna, "use_fill", true, "Fill", "Export strokes with fill enabled");
RNA_def_enum(ot->srna,
"selected_object_type",
select_items,
GP_EXPORT_SELECTED,
"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_normalized_thickness",
false,
"Normalize",
"Export strokes with constant thickness");
}
static void set_export_filepath(bContext *C, wmOperator *op)
{
if (!RNA_struct_property_is_set(op->ptr, "filepath")) {
Main *bmain = CTX_data_main(C);
char filepath[FILE_MAX];
if (BKE_main_blendfile_path(bmain)[0] == '\0') {
BLI_strncpy(filepath, "untitled", sizeof(filepath));
}
else {
BLI_strncpy(filepath, BKE_main_blendfile_path(bmain), sizeof(filepath));
}
BLI_path_extension_replace(filepath, sizeof(filepath), ".pdf");
RNA_string_set(op->ptr, "filepath", filepath);
}
}
/* <-------- SVG single frame export. --------> */
#ifdef WITH_PUGIXML
static bool wm_gpencil_export_svg_common_check(bContext *UNUSED(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 wm_gpencil_export_svg_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
{
set_export_filepath(C, op);
WM_event_add_fileselect(C, op);
return OPERATOR_RUNNING_MODAL;
}
static int wm_gpencil_export_svg_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
Object *ob = CTX_data_active_object(C);
if (!RNA_struct_property_is_set(op->ptr, "filepath")) {
BKE_report(op->reports, RPT_ERROR, "No filename given");
return OPERATOR_CANCELLED;
}
ARegion *region = get_invoke_region(C);
if (region == NULL) {
BKE_report(op->reports, RPT_ERROR, "Unable to find valid 3D View area");
return OPERATOR_CANCELLED;
}
View3D *v3d = get_invoke_view3d(C);
char filename[FILE_MAX];
RNA_string_get(op->ptr, "filepath", filename);
const bool use_fill = RNA_boolean_get(op->ptr, "use_fill");
const bool use_norm_thickness = RNA_boolean_get(op->ptr, "use_normalized_thickness");
const eGpencilExportSelect select_mode = RNA_enum_get(op->ptr, "selected_object_type");
const bool use_clip_camera = RNA_boolean_get(op->ptr, "use_clip_camera");
/* Set flags. */
int flag = 0;
SET_FLAG_FROM_TEST(flag, use_fill, GP_EXPORT_FILL);
SET_FLAG_FROM_TEST(flag, use_norm_thickness, GP_EXPORT_NORM_THICKNESS);
SET_FLAG_FROM_TEST(flag, use_clip_camera, GP_EXPORT_CLIP_CAMERA);
GpencilIOParams params = {.C = C,
.region = region,
.v3d = v3d,
.ob = ob,
.mode = GP_EXPORT_TO_SVG,
.frame_start = CFRA,
.frame_end = CFRA,
.frame_cur = CFRA,
.flag = flag,
.scale = 1.0f,
.select_mode = select_mode,
.frame_mode = GP_EXPORT_FRAME_ACTIVE,
.stroke_sample = RNA_float_get(op->ptr, "stroke_sample"),
.resolution = 1.0f};
/* Do export. */
WM_cursor_wait(true);
const bool done = gpencil_io_export(filename, &params);
WM_cursor_wait(false);
if (!done) {
BKE_report(op->reports, RPT_WARNING, "Unable to export SVG");
}
return OPERATOR_FINISHED;
}
static void ui_gpencil_export_svg_settings(uiLayout *layout, PointerRNA *imfptr)
{
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, imfptr, "selected_object_type", 0, NULL, ICON_NONE);
box = uiLayoutBox(layout);
row = uiLayoutRow(box, false);
uiItemL(row, IFACE_("Export Options"), ICON_NONE);
uiLayout *col = uiLayoutColumn(box, false);
uiItemR(col, imfptr, "stroke_sample", 0, NULL, ICON_NONE);
uiItemR(col, imfptr, "use_fill", 0, NULL, ICON_NONE);
uiItemR(col, imfptr, "use_normalized_thickness", 0, NULL, ICON_NONE);
uiItemR(col, imfptr, "use_clip_camera", 0, NULL, ICON_NONE);
}
static void wm_gpencil_export_svg_draw(bContext *UNUSED(C), wmOperator *op)
{
PointerRNA ptr;
RNA_pointer_create(NULL, op->type->srna, op->properties, &ptr);
ui_gpencil_export_svg_settings(op->layout, &ptr);
}
static bool wm_gpencil_export_svg_poll(bContext *C)
{
if ((CTX_wm_window(C) == NULL) || (CTX_data_mode_enum(C) != CTX_MODE_OBJECT)) {
return false;
}
return true;
}
void WM_OT_gpencil_export_svg(wmOperatorType *ot)
{
ot->name = "Export to SVG";
ot->description = "Export grease pencil to SVG";
ot->idname = "WM_OT_gpencil_export_svg";
ot->invoke = wm_gpencil_export_svg_invoke;
ot->exec = wm_gpencil_export_svg_exec;
ot->poll = wm_gpencil_export_svg_poll;
ot->ui = wm_gpencil_export_svg_draw;
ot->check = wm_gpencil_export_svg_common_check;
WM_operator_properties_filesel(ot,
FILE_TYPE_OBJECT_IO,
FILE_BLENDER,
FILE_SAVE,
WM_FILESEL_FILEPATH | WM_FILESEL_SHOW_PROPS,
FILE_DEFAULTDISPLAY,
FILE_SORT_ALPHA);
gpencil_export_common_props_definition(ot);
RNA_def_boolean(ot->srna,
"use_clip_camera",
false,
"Clip Camera",
"Clip drawings to camera size when export in camera view");
}
#endif
/* <-------- PDF single frame export. --------> */
#ifdef WITH_HARU
static bool wm_gpencil_export_pdf_common_check(bContext *UNUSED(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 wm_gpencil_export_pdf_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
{
set_export_filepath(C, op);
WM_event_add_fileselect(C, op);
return OPERATOR_RUNNING_MODAL;
}
static int wm_gpencil_export_pdf_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
Object *ob = CTX_data_active_object(C);
if (!RNA_struct_property_is_set(op->ptr, "filepath")) {
BKE_report(op->reports, RPT_ERROR, "No filename given");
return OPERATOR_CANCELLED;
}
ARegion *region = get_invoke_region(C);
if (region == NULL) {
BKE_report(op->reports, RPT_ERROR, "Unable to find valid 3D View area");
return OPERATOR_CANCELLED;
}
View3D *v3d = get_invoke_view3d(C);
char filename[FILE_MAX];
RNA_string_get(op->ptr, "filepath", filename);
const bool use_fill = RNA_boolean_get(op->ptr, "use_fill");
const bool use_norm_thickness = RNA_boolean_get(op->ptr, "use_normalized_thickness");
const short select_mode = RNA_enum_get(op->ptr, "selected_object_type");
const short frame_mode = RNA_enum_get(op->ptr, "frame_mode");
/* Set flags. */
int flag = 0;
SET_FLAG_FROM_TEST(flag, use_fill, GP_EXPORT_FILL);
SET_FLAG_FROM_TEST(flag, use_norm_thickness, GP_EXPORT_NORM_THICKNESS);
GpencilIOParams params = {.C = C,
.region = region,
.v3d = v3d,
.ob = ob,
.mode = GP_EXPORT_TO_PDF,
.frame_start = SFRA,
.frame_end = EFRA,
.frame_cur = CFRA,
.flag = flag,
.scale = 1.0f,
.select_mode = select_mode,
.frame_mode = frame_mode,
.stroke_sample = RNA_float_get(op->ptr, "stroke_sample"),
.resolution = 1.0f};
/* Do export. */
WM_cursor_wait(true);
const bool done = gpencil_io_export(filename, &params);
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", 0, NULL, 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", 0, IFACE_("Frame"), ICON_NONE);
uiLayoutSetPropSep(box, true);
sub = uiLayoutColumn(col, true);
uiItemR(sub, imfptr, "stroke_sample", 0, NULL, ICON_NONE);
uiItemR(sub, imfptr, "use_fill", 0, NULL, ICON_NONE);
uiItemR(sub, imfptr, "use_normalized_thickness", 0, NULL, ICON_NONE);
}
static void wm_gpencil_export_pdf_draw(bContext *UNUSED(C), wmOperator *op)
{
PointerRNA ptr;
RNA_pointer_create(NULL, op->type->srna, op->properties, &ptr);
ui_gpencil_export_pdf_settings(op->layout, &ptr);
}
static bool wm_gpencil_export_pdf_poll(bContext *C)
{
if ((CTX_wm_window(C) == NULL) || (CTX_data_mode_enum(C) != CTX_MODE_OBJECT)) {
return false;
}
return true;
}
void WM_OT_gpencil_export_pdf(wmOperatorType *ot)
{
ot->name = "Export to PDF";
ot->description = "Export grease pencil to PDF";
ot->idname = "WM_OT_gpencil_export_pdf";
ot->invoke = wm_gpencil_export_pdf_invoke;
ot->exec = wm_gpencil_export_pdf_exec;
ot->poll = wm_gpencil_export_pdf_poll;
ot->ui = wm_gpencil_export_pdf_draw;
ot->check = wm_gpencil_export_pdf_common_check;
WM_operator_properties_filesel(ot,
FILE_TYPE_OBJECT_IO,
FILE_BLENDER,
FILE_SAVE,
WM_FILESEL_FILEPATH | WM_FILESEL_SHOW_PROPS,
FILE_DEFAULTDISPLAY,
FILE_SORT_ALPHA);
static const EnumPropertyItem gpencil_export_frame_items[] = {
{GP_EXPORT_FRAME_ACTIVE, "ACTIVE", 0, "Active", "Include only active frame"},
{GP_EXPORT_FRAME_SELECTED, "SELECTED", 0, "Selected", "Include selected frames"},
{0, NULL, 0, NULL, NULL},
};
gpencil_export_common_props_definition(ot);
ot->prop = RNA_def_enum(ot->srna,
"frame_mode",
gpencil_export_frame_items,
GP_EXPORT_ACTIVE,
"Frames",
"Which frames to include in the export");
}
#endif

View File

@ -0,0 +1,195 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup editor/io
*/
#include "BLI_path_util.h"
#include "DNA_gpencil_types.h"
#include "DNA_space_types.h"
#include "BKE_context.h"
#include "BKE_gpencil.h"
#include "BKE_report.h"
#include "BLT_translation.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "UI_interface.h"
#include "UI_resources.h"
#include "WM_api.h"
#include "WM_types.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
#include "ED_gpencil.h"
#include "io_gpencil.h"
#include "gpencil_io.h"
/* <-------- SVG single frame import. --------> */
static bool wm_gpencil_import_svg_common_check(bContext *UNUSED(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 wm_gpencil_import_svg_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
{
WM_event_add_fileselect(C, op);
return OPERATOR_RUNNING_MODAL;
}
static int wm_gpencil_import_svg_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
if (!RNA_struct_property_is_set(op->ptr, "filepath")) {
BKE_report(op->reports, RPT_ERROR, "No filename given");
return OPERATOR_CANCELLED;
}
ARegion *region = get_invoke_region(C);
if (region == NULL) {
BKE_report(op->reports, RPT_ERROR, "Unable to find valid 3D View area");
return OPERATOR_CANCELLED;
}
View3D *v3d = get_invoke_view3d(C);
char filename[FILE_MAX];
RNA_string_get(op->ptr, "filepath", filename);
/* Set flags. */
int flag = 0;
const int resolution = RNA_int_get(op->ptr, "resolution");
const float scale = RNA_float_get(op->ptr, "scale");
GpencilIOParams params = {
.C = C,
.region = region,
.v3d = v3d,
.ob = NULL,
.mode = GP_IMPORT_FROM_SVG,
.frame_start = CFRA,
.frame_end = CFRA,
.frame_cur = CFRA,
.flag = flag,
.scale = scale,
.select_mode = 0,
.frame_mode = 0,
.stroke_sample = 0.0f,
.resolution = resolution,
};
/* Do Import. */
WM_cursor_wait(1);
const bool done = gpencil_io_import(filename, &params);
WM_cursor_wait(0);
if (!done) {
BKE_report(op->reports, RPT_WARNING, "Unable to import SVG");
}
return OPERATOR_FINISHED;
}
static void ui_gpencil_import_svg_settings(uiLayout *layout, PointerRNA *imfptr)
{
uiLayoutSetPropSep(layout, true);
uiLayoutSetPropDecorate(layout, false);
uiLayout *col = uiLayoutColumn(layout, false);
uiItemR(col, imfptr, "resolution", 0, NULL, ICON_NONE);
uiItemR(col, imfptr, "scale", 0, NULL, ICON_NONE);
}
static void wm_gpencil_import_svg_draw(bContext *UNUSED(C), wmOperator *op)
{
PointerRNA ptr;
RNA_pointer_create(NULL, op->type->srna, op->properties, &ptr);
ui_gpencil_import_svg_settings(op->layout, &ptr);
}
static bool wm_gpencil_import_svg_poll(bContext *C)
{
if ((CTX_wm_window(C) == NULL) || (CTX_data_mode_enum(C) != CTX_MODE_OBJECT)) {
return false;
}
return true;
}
void WM_OT_gpencil_import_svg(wmOperatorType *ot)
{
ot->name = "Import SVG";
ot->description = "Import SVG into grease pencil";
ot->idname = "WM_OT_gpencil_import_svg";
ot->invoke = wm_gpencil_import_svg_invoke;
ot->exec = wm_gpencil_import_svg_exec;
ot->poll = wm_gpencil_import_svg_poll;
ot->ui = wm_gpencil_import_svg_draw;
ot->check = wm_gpencil_import_svg_common_check;
WM_operator_properties_filesel(ot,
FILE_TYPE_OBJECT_IO,
FILE_BLENDER,
FILE_OPENFILE,
WM_FILESEL_FILEPATH | WM_FILESEL_RELPATH | WM_FILESEL_SHOW_PROPS,
FILE_DEFAULTDISPLAY,
FILE_SORT_DEFAULT);
RNA_def_int(ot->srna,
"resolution",
10,
1,
30,
"Resolution",
"Resolution of the generated strokes",
1,
20);
RNA_def_float(ot->srna,
"scale",
10.0f,
0.001f,
100.0f,
"Scale",
"Scale of the final strokes",
0.001f,
100.0f);
}

View File

@ -0,0 +1,64 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup editor/io
*/
#include "DNA_space_types.h"
#include "BKE_context.h"
#include "BKE_screen.h"
#include "WM_api.h"
#include "io_gpencil.h"
ARegion *get_invoke_region(bContext *C)
{
bScreen *screen = CTX_wm_screen(C);
if (screen == NULL) {
return NULL;
}
ScrArea *area = BKE_screen_find_big_area(screen, SPACE_VIEW3D, 0);
if (area == NULL) {
return NULL;
}
ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW);
return region;
}
View3D *get_invoke_view3d(bContext *C)
{
bScreen *screen = CTX_wm_screen(C);
if (screen == NULL) {
return NULL;
}
ScrArea *area = BKE_screen_find_big_area(screen, SPACE_VIEW3D, 0);
if (area == NULL) {
return NULL;
}
if (area) {
return area->spacedata.first;
}
return NULL;
}

View File

@ -38,6 +38,7 @@
#endif
#include "io_cache.h"
#include "io_gpencil.h"
void ED_operatortypes_io(void)
{
@ -54,6 +55,16 @@ void ED_operatortypes_io(void)
WM_operatortype_append(WM_OT_usd_export);
#endif
WM_operatortype_append(WM_OT_gpencil_import_svg);
#ifdef WITH_PUGIXML
WM_operatortype_append(WM_OT_gpencil_export_svg);
#endif
#ifdef WITH_HARU
WM_operatortype_append(WM_OT_gpencil_export_pdf);
#endif
WM_operatortype_append(CACHEFILE_OT_open);
WM_operatortype_append(CACHEFILE_OT_reload);
}

View File

@ -2543,7 +2543,7 @@ int ED_path_extension_type(const char *path)
if (BLI_path_extension_check(path, ".zip")) {
return FILE_TYPE_ARCHIVE;
}
if (BLI_path_extension_check_n(path, ".obj", ".3ds", ".fbx", ".glb", ".gltf", NULL)) {
if (BLI_path_extension_check_n(path, ".obj", ".3ds", ".fbx", ".glb", ".gltf", ".svg", NULL)) {
return FILE_TYPE_OBJECT_IO;
}
if (BLI_path_extension_check_array(path, imb_ext_image)) {

View File

@ -35,3 +35,5 @@ endif()
if(WITH_USD)
add_subdirectory(usd)
endif()
add_subdirectory(gpencil)

View File

@ -0,0 +1,99 @@
# ***** BEGIN GPL LICENSE BLOCK *****
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# The Original Code is Copyright (C) 2006, Blender Foundation
# All rights reserved.
# ***** END GPL LICENSE BLOCK *****
set(INC
.
../common
../../blenkernel
../../blenlib
../../blenloader
../../bmesh
../../depsgraph
../../editors/include
../../makesdna
../../makesrna
../../windowmanager
../../../../intern/clog
../../../../intern/guardedalloc
../../../../intern/utfconv
)
set(INC_SYS
)
set(SRC
intern/gpencil_io_capi.cc
# This line must be removed if NanoSVG is moved to extern
nanosvg/nanosvg.h
gpencil_io.h
intern/gpencil_io_base.h
intern/gpencil_io_base.cc
intern/gpencil_io_import_base.h
intern/gpencil_io_import_svg.h
intern/gpencil_io_import_base.cc
intern/gpencil_io_import_svg.cc
intern/gpencil_io_export_base.h
)
set(LIB
bf_blenkernel
bf_blenlib
bf_io_common
)
if(WITH_PUGIXML)
list(APPEND SRC
intern/gpencil_io_export_svg.h
intern/gpencil_io_export_svg.cc
)
list(APPEND INC
${PUGIXML_INCLUDE_DIR}
)
list(APPEND LIB
${PUGIXML_LIBRARIES}
)
add_definitions(-DWITH_PUGIXML)
endif()
if(WITH_HARU)
list(APPEND SRC
intern/gpencil_io_export_pdf.h
intern/gpencil_io_export_pdf.cc
)
list(APPEND INC
${HARU_INCLUDE_DIRS}
)
list(APPEND LIB
${HARU_LIBRARIES}
)
add_definitions(-DWITH_HARU)
endif()
list(APPEND LIB
${BOOST_LIBRARIES}
)
blender_add_lib(bf_gpencil "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")

View File

@ -0,0 +1,92 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation
* All rights reserved.
*/
#pragma once
/** \file
* \ingroup bgpencil
*/
#ifdef __cplusplus
extern "C" {
#endif
struct ARegion;
struct bContext;
struct Object;
struct View3D;
typedef struct GpencilIOParams {
bContext *C;
ARegion *region;
View3D *v3d;
/** Grease pencil object. */
Object *ob;
/** Mode (see eGpencilIO_Modes). */
uint16_t mode;
int32_t frame_start;
int32_t frame_end;
int32_t frame_cur;
uint32_t flag;
float scale;
/** Select mode (see eGpencilExportSelect). */
uint16_t select_mode;
/** Frame mode (see eGpencilExportFrame). */
uint16_t frame_mode;
/** Stroke sampling factor. */
float stroke_sample;
int32_t resolution;
} GpencilIOParams;
/* GpencilIOParams->flag. */
typedef enum eGpencilIOParams_Flag {
/* Export Filled strokes. */
GP_EXPORT_FILL = (1 << 0),
/* Export normalized thickness. */
GP_EXPORT_NORM_THICKNESS = (1 << 1),
/* Clip camera area. */
GP_EXPORT_CLIP_CAMERA = (1 << 2),
} eGpencilIOParams_Flag;
typedef enum eGpencilIO_Modes {
GP_EXPORT_TO_SVG = 0,
GP_EXPORT_TO_PDF = 1,
GP_IMPORT_FROM_SVG = 2,
/* Add new formats here. */
} eGpencilIO_Modes;
/* Object to be exported. */
typedef enum eGpencilExportSelect {
GP_EXPORT_ACTIVE = 0,
GP_EXPORT_SELECTED = 1,
GP_EXPORT_VISIBLE = 2,
} eGpencilExportSelect;
/* Framerange to be exported. */
typedef enum eGpencilExportFrame {
GP_EXPORT_FRAME_ACTIVE = 0,
GP_EXPORT_FRAME_SELECTED = 1,
} eGpencilExportFrame;
bool gpencil_io_export(const char *filename, struct GpencilIOParams *iparams);
bool gpencil_io_import(const char *filename, struct GpencilIOParams *iparams);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,386 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation
* All rights reserved.
*/
/** \file
* \ingroup bgpencil
*/
#include "BLI_float2.hh"
#include "BLI_float3.hh"
#include "BLI_float4x4.hh"
#include "BLI_path_util.h"
#include "BLI_span.hh"
#include "DNA_gpencil_types.h"
#include "DNA_layer_types.h"
#include "DNA_material_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "BKE_camera.h"
#include "BKE_context.h"
#include "BKE_gpencil.h"
#include "BKE_gpencil_geom.h"
#include "BKE_main.h"
#include "BKE_material.h"
#include "UI_view2d.h"
#include "ED_view3d.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
#include "gpencil_io_base.h"
using blender::Span;
namespace blender::io::gpencil {
/* Constructor. */
GpencilIO::GpencilIO(const GpencilIOParams *iparams)
{
params_ = *iparams;
/* Easy access data. */
bmain_ = CTX_data_main(params_.C);
depsgraph_ = CTX_data_depsgraph_pointer(params_.C);
scene_ = CTX_data_scene(params_.C);
rv3d_ = (RegionView3D *)params_.region->regiondata;
gpd_ = (params_.ob != nullptr) ? (bGPdata *)params_.ob->data : nullptr;
cfra_ = iparams->frame_cur;
/* Calculate camera matrix. */
Object *cam_ob = params_.v3d->camera;
if (cam_ob != nullptr) {
/* Set up parameters. */
CameraParams params;
BKE_camera_params_init(&params);
BKE_camera_params_from_object(&params, cam_ob);
/* Compute matrix, viewplane, .. */
RenderData *rd = &scene_->r;
BKE_camera_params_compute_viewplane(&params, rd->xsch, rd->ysch, rd->xasp, rd->yasp);
BKE_camera_params_compute_matrix(&params);
float viewmat[4][4];
invert_m4_m4(viewmat, cam_ob->obmat);
mul_m4_m4m4(persmat_, params.winmat, viewmat);
}
else {
unit_m4(persmat_);
}
winx_ = params_.region->winx;
winy_ = params_.region->winy;
/* Camera rectangle. */
if (rv3d_->persp == RV3D_CAMOB) {
render_x_ = (scene_->r.xsch * scene_->r.size) / 100;
render_y_ = (scene_->r.ysch * scene_->r.size) / 100;
ED_view3d_calc_camera_border(CTX_data_scene(params_.C),
depsgraph_,
params_.region,
params_.v3d,
rv3d_,
&camera_rect_,
true);
is_camera_ = true;
camera_ratio_ = render_x_ / (camera_rect_.xmax - camera_rect_.xmin);
offset_.x = camera_rect_.xmin;
offset_.y = camera_rect_.ymin;
}
else {
is_camera_ = false;
/* Calc selected object boundbox. Need set initial value to some variables. */
camera_ratio_ = 1.0f;
offset_.x = 0.0f;
offset_.y = 0.0f;
selected_objects_boundbox_calc();
rctf boundbox;
selected_objects_boundbox_get(&boundbox);
render_x_ = boundbox.xmax - boundbox.xmin;
render_y_ = boundbox.ymax - boundbox.ymin;
offset_.x = boundbox.xmin;
offset_.y = boundbox.ymin;
}
}
/** Create a list of selected objects sorted from back to front */
void GpencilIO::create_object_list()
{
ViewLayer *view_layer = CTX_data_view_layer(params_.C);
float3 camera_z_axis;
copy_v3_v3(camera_z_axis, rv3d_->viewinv[2]);
ob_list_.clear();
LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) {
Object *object = base->object;
if (object->type != OB_GPENCIL) {
continue;
}
if ((params_.select_mode == GP_EXPORT_ACTIVE) && (params_.ob != object)) {
continue;
}
if ((params_.select_mode == GP_EXPORT_SELECTED) && ((base->flag & BASE_SELECTED) == 0)) {
continue;
}
/* Save z-depth from view to sort from back to front. */
if (is_camera_) {
float camera_z = dot_v3v3(camera_z_axis, object->obmat[3]);
ObjectZ obz = {camera_z, object};
ob_list_.append(obz);
}
else {
float zdepth = 0;
if (rv3d_) {
if (rv3d_->is_persp) {
zdepth = ED_view3d_calc_zfac(rv3d_, object->obmat[3], nullptr);
}
else {
zdepth = -dot_v3v3(rv3d_->viewinv[2], object->obmat[3]);
}
ObjectZ obz = {zdepth * -1.0f, object};
ob_list_.append(obz);
}
}
}
/* Sort list of objects from point of view. */
std::sort(ob_list_.begin(), ob_list_.end(), [](const ObjectZ &obz1, const ObjectZ &obz2) {
return obz1.zdepth < obz2.zdepth;
});
}
/**
* Set file input_text full path.
* \param filename: Path of the file provided by save dialog.
*/
void GpencilIO::filename_set(const char *filename)
{
BLI_strncpy(filename_, filename, FILE_MAX);
BLI_path_abs(filename_, BKE_main_blendfile_path(bmain_));
}
/** Convert to screenspace. */
bool GpencilIO::gpencil_3D_point_to_screen_space(const float3 co, float2 &r_co)
{
float3 parent_co = diff_mat_ * co;
float2 screen_co;
eV3DProjTest test = (eV3DProjTest)(V3D_PROJ_RET_OK);
if (ED_view3d_project_float_global(params_.region, parent_co, screen_co, test) ==
V3D_PROJ_RET_OK) {
if (!ELEM(V2D_IS_CLIPPED, screen_co[0], screen_co[1])) {
copy_v2_v2(r_co, screen_co);
/* Invert X axis. */
if (invert_axis_[0]) {
r_co[0] = winx_ - r_co[0];
}
/* Invert Y axis. */
if (invert_axis_[1]) {
r_co[1] = winy_ - r_co[1];
}
/* Apply offset and scale. */
sub_v2_v2(r_co, &offset_.x);
mul_v2_fl(r_co, camera_ratio_);
return true;
}
}
r_co[0] = V2D_IS_CLIPPED;
r_co[1] = V2D_IS_CLIPPED;
/* Invert X axis. */
if (invert_axis_[0]) {
r_co[0] = winx_ - r_co[0];
}
/* Invert Y axis. */
if (invert_axis_[1]) {
r_co[1] = winy_ - r_co[1];
}
return false;
}
/** Convert to render space. */
float2 GpencilIO::gpencil_3D_point_to_render_space(const float3 co)
{
float3 parent_co = diff_mat_ * co;
mul_m4_v3(persmat_, parent_co);
parent_co.x = parent_co.x / max_ff(FLT_MIN, parent_co[2]);
parent_co.y = parent_co.y / max_ff(FLT_MIN, parent_co[2]);
float2 r_co;
r_co.x = (parent_co.x + 1.0f) / 2.0f * (float)render_x_;
r_co.y = (parent_co.y + 1.0f) / 2.0f * (float)render_y_;
/* Invert X axis. */
if (invert_axis_[0]) {
r_co.x = (float)render_x_ - r_co.x;
}
/* Invert Y axis. */
if (invert_axis_[1]) {
r_co.y = (float)render_y_ - r_co.y;
}
return r_co;
}
/** Convert to 2D. */
float2 GpencilIO::gpencil_3D_point_to_2D(const float3 co)
{
const bool is_camera = (bool)(rv3d_->persp == RV3D_CAMOB);
if (is_camera) {
return gpencil_3D_point_to_render_space(co);
}
float2 result;
gpencil_3D_point_to_screen_space(co, result);
return result;
}
/** Get radius of point. */
float GpencilIO::stroke_point_radius_get(bGPDlayer *gpl, bGPDstroke *gps)
{
bGPDspoint *pt = &gps->points[0];
const float2 screen_co = gpencil_3D_point_to_2D(&pt->x);
/* Radius. */
bGPDstroke *gps_perimeter = BKE_gpencil_stroke_perimeter_from_view(
rv3d_, gpd_, gpl, gps, 3, diff_mat_.values);
pt = &gps_perimeter->points[0];
const float2 screen_ex = gpencil_3D_point_to_2D(&pt->x);
const float2 v1 = screen_co - screen_ex;
float radius = v1.length();
BKE_gpencil_free_stroke(gps_perimeter);
return MAX2(radius, 1.0f);
}
void GpencilIO::prepare_layer_export_matrix(Object *ob, bGPDlayer *gpl)
{
BKE_gpencil_layer_transform_matrix_get(depsgraph_, ob, gpl, diff_mat_.values);
diff_mat_ = diff_mat_ * float4x4(gpl->layer_invmat);
}
void GpencilIO::prepare_stroke_export_colors(Object *ob, bGPDstroke *gps)
{
MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1);
/* Stroke color. */
copy_v4_v4(stroke_color_, gp_style->stroke_rgba);
avg_opacity_ = 0;
/* Get average vertex color and apply. */
float avg_color[4] = {0.0f, 0.0f, 0.0f, 0.0f};
for (const bGPDspoint &pt : Span(gps->points, gps->totpoints)) {
add_v4_v4(avg_color, pt.vert_color);
avg_opacity_ += pt.strength;
}
mul_v4_v4fl(avg_color, avg_color, 1.0f / (float)gps->totpoints);
interp_v3_v3v3(stroke_color_, stroke_color_, avg_color, avg_color[3]);
avg_opacity_ /= (float)gps->totpoints;
/* Fill color. */
copy_v4_v4(fill_color_, gp_style->fill_rgba);
/* Apply vertex color for fill. */
interp_v3_v3v3(fill_color_, fill_color_, gps->vert_color_fill, gps->vert_color_fill[3]);
}
float GpencilIO::stroke_average_opacity_get()
{
return avg_opacity_;
}
bool GpencilIO::is_camera_mode()
{
return is_camera_;
}
/* Calculate selected strokes boundbox. */
void GpencilIO::selected_objects_boundbox_calc()
{
const float gap = 10.0f;
float2 min, max;
INIT_MINMAX2(min, max);
for (ObjectZ &obz : ob_list_) {
Object *ob = obz.ob;
/* Use evaluated version to get strokes with modifiers. */
Object *ob_eval = (Object *)DEG_get_evaluated_id(depsgraph_, &ob->id);
bGPdata *gpd_eval = (bGPdata *)ob_eval->data;
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd_eval->layers) {
if (gpl->flag & GP_LAYER_HIDE) {
continue;
}
BKE_gpencil_layer_transform_matrix_get(depsgraph_, ob_eval, gpl, diff_mat_.values);
bGPDframe *gpf = gpl->actframe;
if (gpf == nullptr) {
continue;
}
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
if (gps->totpoints == 0) {
continue;
}
for (const bGPDspoint &pt : MutableSpan(gps->points, gps->totpoints)) {
const float2 screen_co = gpencil_3D_point_to_2D(&pt.x);
minmax_v2v2_v2(min, max, screen_co);
}
}
}
}
/* Add small gap. */
add_v2_fl(min, gap * -1.0f);
add_v2_fl(max, gap);
select_boundbox_.xmin = min[0];
select_boundbox_.ymin = min[1];
select_boundbox_.xmax = max[0];
select_boundbox_.ymax = max[1];
}
void GpencilIO::selected_objects_boundbox_get(rctf *boundbox)
{
boundbox->xmin = select_boundbox_.xmin;
boundbox->xmax = select_boundbox_.xmax;
boundbox->ymin = select_boundbox_.ymin;
boundbox->ymax = select_boundbox_.ymax;
}
void GpencilIO::frame_number_set(const int value)
{
cfra_ = value;
}
} // namespace blender::io::gpencil

View File

@ -0,0 +1,116 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation
* All rights reserved.
*/
#pragma once
/** \file
* \ingroup bgpencil
*/
#include "BLI_float2.hh"
#include "BLI_float3.hh"
#include "BLI_float4x4.hh"
#include "BLI_vector.hh"
#include "DNA_space_types.h" /* for FILE_MAX */
#include "gpencil_io.h"
struct Depsgraph;
struct Main;
struct Object;
struct RegionView3D;
struct Scene;
struct bGPdata;
struct bGPDlayer;
struct bGPDstroke;
using blender::Vector;
namespace blender::io::gpencil {
class GpencilIO {
public:
GpencilIO(const GpencilIOParams *iparams);
void frame_number_set(const int value);
protected:
GpencilIOParams params_;
bool invert_axis_[2];
float4x4 diff_mat_;
char filename_[FILE_MAX];
/* Used for sorting objects. */
struct ObjectZ {
float zdepth;
struct Object *ob;
};
/** List of included objects. */
blender::Vector<ObjectZ> ob_list_;
/* Data for easy access. */
struct Depsgraph *depsgraph_;
struct bGPdata *gpd_;
struct Main *bmain_;
struct Scene *scene_;
struct RegionView3D *rv3d_;
int16_t winx_, winy_;
int16_t render_x_, render_y_;
float camera_ratio_;
rctf camera_rect_;
float2 offset_;
int cfra_;
float stroke_color_[4], fill_color_[4];
/* Geometry functions. */
bool gpencil_3D_point_to_screen_space(const float3 co, float2 &r_co);
float2 gpencil_3D_point_to_render_space(const float3 co);
float2 gpencil_3D_point_to_2D(const float3 co);
float stroke_point_radius_get(struct bGPDlayer *gpl, struct bGPDstroke *gps);
void create_object_list();
bool is_camera_mode();
float stroke_average_opacity_get();
void prepare_layer_export_matrix(struct Object *ob, struct bGPDlayer *gpl);
void prepare_stroke_export_colors(struct Object *ob, struct bGPDstroke *gps);
void selected_objects_boundbox_calc();
void selected_objects_boundbox_get(rctf *boundbox);
void filename_set(const char *filename);
private:
float avg_opacity_;
bool is_camera_;
rctf select_boundbox_;
/* Camera matrix. */
float persmat_[4][4];
};
} // namespace blender::io::gpencil

View File

@ -0,0 +1,202 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation
* All rights reserved.
*/
/** \file
* \ingroup bgpencil
*/
#include <stdio.h>
#include "BLI_listbase.h"
#include "DNA_gpencil_types.h"
#include "DNA_screen_types.h"
#include "DNA_space_types.h"
#include "BKE_context.h"
#include "BKE_gpencil.h"
#include "BKE_main.h"
#include "BKE_scene.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
#include "../gpencil_io.h"
#ifdef WITH_HARU
# include "gpencil_io_export_pdf.h"
#endif
#ifdef WITH_PUGIXML
# include "gpencil_io_export_svg.h"
#endif
#include "gpencil_io_import_svg.h"
#ifdef WITH_HARU
using blender::io::gpencil::GpencilExporterPDF;
#endif
#ifdef WITH_PUGIXML
using blender::io::gpencil::GpencilExporterSVG;
#endif
using blender::io::gpencil::GpencilImporterSVG;
/* Check if frame is included. */
static bool is_keyframe_included(bGPdata *gpd_, const int32_t framenum, const bool use_selected)
{
/* Check if exist a frame. */
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd_->layers) {
if (gpl->flag & GP_LAYER_HIDE) {
continue;
}
LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
if (gpf->framenum == framenum) {
if ((!use_selected) || (use_selected && (gpf->flag & GP_FRAME_SELECT))) {
return true;
}
}
}
}
return false;
}
/* Import frame. */
static bool gpencil_io_import_frame(void *in_importer, const GpencilIOParams &iparams)
{
bool result = false;
switch (iparams.mode) {
case GP_IMPORT_FROM_SVG: {
GpencilImporterSVG *importer = (GpencilImporterSVG *)in_importer;
result |= importer->read();
break;
}
/* Add new import formats here. */
default:
break;
}
return result;
}
/* Export frame in PDF. */
#ifdef WITH_HARU
static bool gpencil_io_export_pdf(Depsgraph *depsgraph,
Scene *scene,
Object *ob,
GpencilExporterPDF *exporter,
const GpencilIOParams *iparams)
{
bool result = false;
Object *ob_eval_ = (Object *)DEG_get_evaluated_id(depsgraph, &ob->id);
bGPdata *gpd_eval = (bGPdata *)ob_eval_->data;
exporter->frame_number_set(iparams->frame_cur);
result |= exporter->new_document();
const bool use_frame_selected = (iparams->frame_mode == GP_EXPORT_FRAME_SELECTED);
if (use_frame_selected) {
for (int32_t i = iparams->frame_start; i < iparams->frame_end + 1; i++) {
if (!is_keyframe_included(gpd_eval, i, use_frame_selected)) {
continue;
}
CFRA = i;
BKE_scene_graph_update_for_newframe(depsgraph);
exporter->frame_number_set(i);
exporter->add_newpage();
exporter->add_body();
}
result = exporter->write();
/* Back to original frame. */
exporter->frame_number_set(iparams->frame_cur);
CFRA = iparams->frame_cur;
BKE_scene_graph_update_for_newframe(depsgraph);
}
else {
exporter->add_newpage();
exporter->add_body();
result = exporter->write();
}
return result;
}
#endif
/* Export current frame in SVG. */
#ifdef WITH_PUGIXML
static bool gpencil_io_export_frame_svg(GpencilExporterSVG *exporter,
const GpencilIOParams *iparams,
const bool newpage,
const bool body,
const bool savepage)
{
bool result = false;
exporter->frame_number_set(iparams->frame_cur);
if (newpage) {
result |= exporter->add_newpage();
}
if (body) {
result |= exporter->add_body();
}
if (savepage) {
result = exporter->write();
}
return result;
}
#endif
/* Main import entry point function. */
bool gpencil_io_import(const char *filename, GpencilIOParams *iparams)
{
GpencilImporterSVG importer = GpencilImporterSVG(filename, iparams);
return gpencil_io_import_frame(&importer, *iparams);
}
/* Main export entry point function. */
bool gpencil_io_export(const char *filename, GpencilIOParams *iparams)
{
Depsgraph *depsgraph_ = CTX_data_depsgraph_pointer(iparams->C);
Scene *scene_ = CTX_data_scene(iparams->C);
Object *ob = CTX_data_active_object(iparams->C);
UNUSED_VARS(depsgraph_, scene_, ob);
switch (iparams->mode) {
#ifdef WITH_PUGIXML
case GP_EXPORT_TO_SVG: {
GpencilExporterSVG exporter = GpencilExporterSVG(filename, iparams);
return gpencil_io_export_frame_svg(&exporter, iparams, true, true, true);
break;
}
#endif
#ifdef WITH_HARU
case GP_EXPORT_TO_PDF: {
GpencilExporterPDF exporter = GpencilExporterPDF(filename, iparams);
return gpencil_io_export_pdf(depsgraph_, scene_, ob, &exporter, iparams);
break;
}
#endif
/* Add new export formats here. */
default:
break;
}
return false;
}

View File

@ -0,0 +1,38 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation
* All rights reserved.
*/
#pragma once
/** \file
* \ingroup bgpencil
*/
#include "gpencil_io_base.h"
namespace blender::io::gpencil {
class GpencilExporter : public GpencilIO {
public:
GpencilExporter(const struct GpencilIOParams *iparams) : GpencilIO(iparams){};
virtual bool write() = 0;
protected:
private:
};
} // namespace blender::io::gpencil

View File

@ -0,0 +1,311 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation
* All rights reserved.
*/
/** \file
* \ingroup bgpencil
*/
#include "BLI_math_vector.h"
#include "DNA_gpencil_types.h"
#include "DNA_material_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "DNA_view3d_types.h"
#include "BKE_context.h"
#include "BKE_gpencil.h"
#include "BKE_gpencil_geom.h"
#include "BKE_main.h"
#include "BKE_material.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
#include "ED_gpencil.h"
#include "ED_view3d.h"
#ifdef WIN32
# include "utfconv.h"
#endif
#include "UI_view2d.h"
#include "gpencil_io.h"
#include "gpencil_io_export_pdf.h"
namespace blender ::io ::gpencil {
static void error_handler(HPDF_STATUS error_no, HPDF_STATUS detail_no, void *UNUSED(user_data))
{
printf("ERROR: error_no=%04X, detail_no=%u\n", (HPDF_UINT)error_no, (HPDF_UINT)detail_no);
}
/* Constructor. */
GpencilExporterPDF::GpencilExporterPDF(const char *filename, const GpencilIOParams *iparams)
: GpencilExporter(iparams)
{
filename_set(filename);
invert_axis_[0] = false;
invert_axis_[1] = false;
pdf_ = nullptr;
page_ = nullptr;
gstate_ = nullptr;
}
bool GpencilExporterPDF::new_document()
{
return create_document();
}
bool GpencilExporterPDF::add_newpage()
{
return add_page();
}
bool GpencilExporterPDF::add_body()
{
export_gpencil_layers();
return true;
}
bool GpencilExporterPDF::write()
{
/* Support unicode character paths on Windows. */
HPDF_STATUS res = 0;
/* TODO: It looks libharu does not support unicode. */
//#ifdef WIN32
// char filename_cstr[FILE_MAX];
// BLI_strncpy(filename_cstr, filename_, FILE_MAX);
//
// UTF16_ENCODE(filename_cstr);
// std::wstring wstr(filename_cstr_16);
// res = HPDF_SaveToFile(pdf_, wstr.c_str());
//
// UTF16_UN_ENCODE(filename_cstr);
//#else
res = HPDF_SaveToFile(pdf_, filename_);
//#endif
return (res == 0) ? true : false;
}
/* Create pdf document. */
bool GpencilExporterPDF::create_document()
{
pdf_ = HPDF_New(error_handler, nullptr);
if (!pdf_) {
std::cout << "error: cannot create PdfDoc object\n";
return false;
}
return true;
}
/* Add page. */
bool GpencilExporterPDF::add_page()
{
/* Add a new page object. */
page_ = HPDF_AddPage(pdf_);
if (!pdf_) {
std::cout << "error: cannot create PdfPage\n";
return false;
}
HPDF_Page_SetWidth(page_, render_x_);
HPDF_Page_SetHeight(page_, render_y_);
return true;
}
/* Main layer loop. */
void GpencilExporterPDF::export_gpencil_layers()
{
/* If is doing a set of frames, the list of objects can change for each frame. */
create_object_list();
const bool is_normalized = ((params_.flag & GP_EXPORT_NORM_THICKNESS) != 0);
for (ObjectZ &obz : ob_list_) {
Object *ob = obz.ob;
/* Use evaluated version to get strokes with modifiers. */
Object *ob_eval_ = (Object *)DEG_get_evaluated_id(depsgraph_, &ob->id);
bGPdata *gpd_eval = (bGPdata *)ob_eval_->data;
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd_eval->layers) {
if (gpl->flag & GP_LAYER_HIDE) {
continue;
}
prepare_layer_export_matrix(ob, gpl);
bGPDframe *gpf = gpl->actframe;
if ((gpf == nullptr) || (gpf->strokes.first == nullptr)) {
continue;
}
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
if (gps->totpoints < 2) {
continue;
}
if (!ED_gpencil_stroke_material_visible(ob, gps)) {
continue;
}
/* Duplicate the stroke to apply any layer thickness change. */
bGPDstroke *gps_duplicate = BKE_gpencil_stroke_duplicate(gps, true, false);
MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob,
gps_duplicate->mat_nr + 1);
const bool is_stroke = ((gp_style->flag & GP_MATERIAL_STROKE_SHOW) &&
(gp_style->stroke_rgba[3] > GPENCIL_ALPHA_OPACITY_THRESH));
const bool is_fill = ((gp_style->flag & GP_MATERIAL_FILL_SHOW) &&
(gp_style->fill_rgba[3] > GPENCIL_ALPHA_OPACITY_THRESH));
prepare_stroke_export_colors(ob, gps_duplicate);
/* Apply layer thickness change. */
gps_duplicate->thickness += gpl->line_change;
/* Apply object scale to thickness. */
gps_duplicate->thickness *= mat4_to_scale(ob->obmat);
CLAMP_MIN(gps_duplicate->thickness, 1.0f);
/* Fill. */
if ((is_fill) && (params_.flag & GP_EXPORT_FILL)) {
/* Fill is exported as polygon for fill and stroke in a different shape. */
export_stroke_to_polyline(gpl, gps_duplicate, is_stroke, true, false);
}
/* Stroke. */
if (is_stroke) {
if (is_normalized) {
export_stroke_to_polyline(gpl, gps_duplicate, is_stroke, false, true);
}
else {
bGPDstroke *gps_perimeter = BKE_gpencil_stroke_perimeter_from_view(
rv3d_, gpd_, gpl, gps_duplicate, 3, diff_mat_.values);
/* Sample stroke. */
if (params_.stroke_sample > 0.0f) {
BKE_gpencil_stroke_sample(gpd_eval, gps_perimeter, params_.stroke_sample, false);
}
export_stroke_to_polyline(gpl, gps_perimeter, is_stroke, false, false);
BKE_gpencil_free_stroke(gps_perimeter);
}
}
BKE_gpencil_free_stroke(gps_duplicate);
}
}
}
}
/**
* Export a stroke using polyline or polygon
* \param do_fill: True if the stroke is only fill
*/
void GpencilExporterPDF::export_stroke_to_polyline(bGPDlayer *gpl,
bGPDstroke *gps,
const bool is_stroke,
const bool do_fill,
const bool normalize)
{
const bool cyclic = ((gps->flag & GP_STROKE_CYCLIC) != 0);
const float avg_pressure = BKE_gpencil_stroke_average_pressure_get(gps);
/* Get the thickness in pixels using a simple 1 point stroke. */
bGPDstroke *gps_temp = BKE_gpencil_stroke_duplicate(gps, false, false);
gps_temp->totpoints = 1;
gps_temp->points = (bGPDspoint *)MEM_callocN(sizeof(bGPDspoint), "gp_stroke_points");
const bGPDspoint *pt_src = &gps->points[0];
bGPDspoint *pt_dst = &gps_temp->points[0];
copy_v3_v3(&pt_dst->x, &pt_src->x);
pt_dst->pressure = avg_pressure;
const float radius = stroke_point_radius_get(gpl, gps_temp);
BKE_gpencil_free_stroke(gps_temp);
color_set(gpl, do_fill);
if (is_stroke && !do_fill) {
HPDF_Page_SetLineJoin(page_, HPDF_ROUND_JOIN);
HPDF_Page_SetLineWidth(page_, MAX2((radius * 2.0f) - gpl->line_change, 1.0f));
}
/* Loop all points. */
for (const int i : IndexRange(gps->totpoints)) {
bGPDspoint *pt = &gps->points[i];
const float2 screen_co = gpencil_3D_point_to_2D(&pt->x);
if (i == 0) {
HPDF_Page_MoveTo(page_, screen_co.x, screen_co.y);
}
else {
HPDF_Page_LineTo(page_, screen_co.x, screen_co.y);
}
}
/* Close cyclic */
if (cyclic) {
HPDF_Page_ClosePath(page_);
}
if (do_fill || !normalize) {
HPDF_Page_Fill(page_);
}
else {
HPDF_Page_Stroke(page_);
}
HPDF_Page_GRestore(page_);
}
/**
* Set color
* @param do_fill: True if the stroke is only fill
*/
void GpencilExporterPDF::color_set(bGPDlayer *gpl, const bool do_fill)
{
const float fill_opacity = fill_color_[3] * gpl->opacity;
const float stroke_opacity = stroke_color_[3] * stroke_average_opacity_get() * gpl->opacity;
HPDF_Page_GSave(page_);
gstate_ = HPDF_CreateExtGState(pdf_);
float col[3];
if (do_fill) {
interp_v3_v3v3(col, fill_color_, gpl->tintcolor, gpl->tintcolor[3]);
linearrgb_to_srgb_v3_v3(col, col);
CLAMP3(col, 0.0f, 1.0f);
HPDF_ExtGState_SetAlphaFill(gstate_, clamp_f(fill_opacity, 0.0f, 1.0f));
HPDF_Page_SetRGBFill(page_, col[0], col[1], col[2]);
}
else {
interp_v3_v3v3(col, stroke_color_, gpl->tintcolor, gpl->tintcolor[3]);
linearrgb_to_srgb_v3_v3(col, col);
CLAMP3(col, 0.0f, 1.0f);
HPDF_ExtGState_SetAlphaFill(gstate_, clamp_f(stroke_opacity, 0.0f, 1.0f));
HPDF_ExtGState_SetAlphaStroke(gstate_, clamp_f(stroke_opacity, 0.0f, 1.0f));
HPDF_Page_SetRGBFill(page_, col[0], col[1], col[2]);
HPDF_Page_SetRGBStroke(page_, col[0], col[1], col[2]);
}
HPDF_Page_SetExtGState(page_, gstate_);
}
} // namespace blender::io::gpencil

View File

@ -0,0 +1,67 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation
* All rights reserved.
*/
#pragma once
/** \file
* \ingroup bgpencil
*/
#include "gpencil_io_export_base.h"
#include "hpdf.h"
struct GpencilIOParams;
struct bGPDlayer;
struct bGPDstroke;
#define PDF_EXPORTER_NAME "PDF Exporter for Grease Pencil"
#define PDF_EXPORTER_VERSION "v1.0"
namespace blender::io::gpencil {
class GpencilExporterPDF : public GpencilExporter {
public:
GpencilExporterPDF(const char *filename, const struct GpencilIOParams *iparams);
bool new_document();
bool add_newpage();
bool add_body();
bool write();
protected:
private:
/* PDF document. */
HPDF_Doc pdf_;
/* PDF page. */
HPDF_Page page_;
/* State. */
HPDF_ExtGState gstate_;
bool create_document();
bool add_page();
void export_gpencil_layers();
void export_stroke_to_polyline(bGPDlayer *gpl,
bGPDstroke *gps,
const bool is_stroke,
const bool do_fill,
const bool normalize);
void color_set(bGPDlayer *gpl, const bool do_fill);
};
} // namespace blender::io::gpencil

View File

@ -0,0 +1,464 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation
* All rights reserved.
*/
/** \file
* \ingroup bgpencil
*/
#include "BLI_math_vector.h"
#include "BLI_string.h"
#include "BLI_utildefines.h"
#include "DNA_gpencil_types.h"
#include "DNA_material_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "BKE_gpencil.h"
#include "BKE_gpencil_geom.h"
#include "BKE_main.h"
#include "BKE_material.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
#include "ED_gpencil.h"
#include "ED_view3d.h"
#ifdef WIN32
# include "utfconv.h"
#endif
#include "UI_view2d.h"
#include "gpencil_io.h"
#include "gpencil_io_export_svg.h"
#include "pugixml.hpp"
namespace blender ::io ::gpencil {
/* Constructor. */
GpencilExporterSVG::GpencilExporterSVG(const char *filename, const GpencilIOParams *iparams)
: GpencilExporter(iparams)
{
filename_set(filename);
invert_axis_[0] = false;
invert_axis_[1] = true;
}
bool GpencilExporterSVG::add_newpage()
{
create_document_header();
return true;
}
bool GpencilExporterSVG::add_body()
{
export_gpencil_layers();
return true;
}
bool GpencilExporterSVG::write()
{
bool result = true;
/* Support unicode character paths on Windows. */
#ifdef WIN32
char filename_cstr[FILE_MAX];
BLI_strncpy(filename_cstr, filename_, FILE_MAX);
UTF16_ENCODE(filename_cstr);
std::wstring wstr(filename_cstr_16);
result = main_doc_.save_file(wstr.c_str());
UTF16_UN_ENCODE(filename_cstr);
#else
result = main_doc_.save_file(filename_);
#endif
return result;
}
/* Create document header and main svg node. */
void GpencilExporterSVG::create_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);
char txt[128];
sprintf(txt, " Generator: Blender, %s - %s ", SVG_EXPORTER_NAME, SVG_EXPORTER_VERSION);
comment.set_value(txt);
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\"");
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");
std::string width;
std::string height;
width = std::to_string(render_x_);
height = std::to_string(render_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());
}
/* Main layer loop. */
void GpencilExporterSVG::export_gpencil_layers()
{
const bool is_clipping = is_camera_mode() && (params_.flag & GP_EXPORT_CLIP_CAMERA) != 0;
/* If is doing a set of frames, the list of objects can change for each frame. */
create_object_list();
for (ObjectZ &obz : ob_list_) {
Object *ob = obz.ob;
/* Camera clipping. */
if (is_clipping) {
pugi::xml_node clip_node = main_node_.append_child("clipPath");
clip_node.append_attribute("id").set_value(("clip-path" + std::to_string(cfra_)).c_str());
add_rect(clip_node, 0, 0, render_x_, render_y_, 0.0f, "#000000");
}
frame_node_ = main_node_.append_child("g");
std::string frametxt = "blender_frame_" + std::to_string(cfra_);
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(cfra_) + ")").c_str());
}
pugi::xml_node ob_node = frame_node_.append_child("g");
char obtxt[96];
sprintf(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_ = (Object *)DEG_get_evaluated_id(depsgraph_, &ob->id);
bGPdata *gpd_eval = (bGPdata *)ob_eval_->data;
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd_eval->layers) {
if (gpl->flag & GP_LAYER_HIDE) {
continue;
}
prepare_layer_export_matrix(ob, gpl);
bGPDframe *gpf = gpl->actframe;
if ((gpf == nullptr) || (gpf->strokes.first == nullptr)) {
continue;
}
/* Layer node. */
std::string txt = "Layer: ";
txt.append(gpl->info);
ob_node.append_child(pugi::node_comment).set_value(txt.c_str());
pugi::xml_node node_gpl = ob_node.append_child("g");
node_gpl.append_attribute("id").set_value(gpl->info);
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
if (gps->totpoints < 2) {
continue;
}
if (!ED_gpencil_stroke_material_visible(ob, gps)) {
continue;
}
/* Duplicate the stroke to apply any layer thickness change. */
bGPDstroke *gps_duplicate = BKE_gpencil_stroke_duplicate(gps, true, false);
MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob,
gps_duplicate->mat_nr + 1);
const bool is_stroke = ((gp_style->flag & GP_MATERIAL_STROKE_SHOW) &&
(gp_style->stroke_rgba[3] > GPENCIL_ALPHA_OPACITY_THRESH));
const bool is_fill = ((gp_style->flag & GP_MATERIAL_FILL_SHOW) &&
(gp_style->fill_rgba[3] > GPENCIL_ALPHA_OPACITY_THRESH));
prepare_stroke_export_colors(ob, gps_duplicate);
/* Apply layer thickness change. */
gps_duplicate->thickness += gpl->line_change;
/* Apply object scale to thickness. */
gps_duplicate->thickness *= mat4_to_scale(ob->obmat);
CLAMP_MIN(gps_duplicate->thickness, 1.0f);
const bool is_normalized = ((params_.flag & GP_EXPORT_NORM_THICKNESS) != 0) ||
BKE_gpencil_stroke_is_pressure_constant(gps);
/* Fill. */
if ((is_fill) && (params_.flag & GP_EXPORT_FILL)) {
/* Fill is always exported as polygon because the stroke of the fill is done
* in a different SVG command. */
export_stroke_to_polyline(gpl, gps_duplicate, node_gpl, is_stroke, true);
}
/* Stroke. */
if (is_stroke) {
if (is_normalized) {
export_stroke_to_polyline(gpl, gps_duplicate, node_gpl, is_stroke, false);
}
else {
bGPDstroke *gps_perimeter = BKE_gpencil_stroke_perimeter_from_view(
rv3d_, gpd_, gpl, gps_duplicate, 3, diff_mat_.values);
/* Sample stroke. */
if (params_.stroke_sample > 0.0f) {
BKE_gpencil_stroke_sample(gpd_eval, gps_perimeter, params_.stroke_sample, false);
}
export_stroke_to_path(gpl, gps_perimeter, node_gpl, false);
BKE_gpencil_free_stroke(gps_perimeter);
}
}
BKE_gpencil_free_stroke(gps_duplicate);
}
}
}
}
/**
* Export a stroke using SVG path
* \param node_gpl: Node of the layer.
* \param do_fill: True if the stroke is only fill
*/
void GpencilExporterSVG::export_stroke_to_path(bGPDlayer *gpl,
bGPDstroke *gps,
pugi::xml_node node_gpl,
const bool do_fill)
{
pugi::xml_node node_gps = node_gpl.append_child("path");
float col[3];
std::string stroke_hex;
if (do_fill) {
node_gps.append_attribute("fill-opacity").set_value(fill_color_[3] * gpl->opacity);
interp_v3_v3v3(col, fill_color_, gpl->tintcolor, gpl->tintcolor[3]);
}
else {
node_gps.append_attribute("fill-opacity")
.set_value(stroke_color_[3] * stroke_average_opacity_get() * gpl->opacity);
interp_v3_v3v3(col, stroke_color_, gpl->tintcolor, gpl->tintcolor[3]);
}
linearrgb_to_srgb_v3_v3(col, col);
stroke_hex = rgb_to_hexstr(col);
node_gps.append_attribute("fill").set_value(stroke_hex.c_str());
node_gps.append_attribute("stroke").set_value("none");
std::string txt = "M";
for (const int i : IndexRange(gps->totpoints)) {
if (i > 0) {
txt.append("L");
}
bGPDspoint &pt = gps->points[i];
const float2 screen_co = gpencil_3D_point_to_2D(&pt.x);
txt.append(std::to_string(screen_co.x) + "," + std::to_string(screen_co.y));
}
/* Close patch (cyclic)*/
if (gps->flag & GP_STROKE_CYCLIC) {
txt.append("z");
}
node_gps.append_attribute("d").set_value(txt.c_str());
}
/**
* Export a stroke using polyline or polygon
* \param node_gpl: Node of the layer.
* \param do_fill: True if the stroke is only fill
*/
void GpencilExporterSVG::export_stroke_to_polyline(bGPDlayer *gpl,
bGPDstroke *gps,
pugi::xml_node node_gpl,
const bool is_stroke,
const bool do_fill)
{
const bool cyclic = ((gps->flag & GP_STROKE_CYCLIC) != 0);
const float avg_pressure = BKE_gpencil_stroke_average_pressure_get(gps);
/* Get the thickness in pixels using a simple 1 point stroke. */
bGPDstroke *gps_temp = BKE_gpencil_stroke_duplicate(gps, false, false);
gps_temp->totpoints = 1;
gps_temp->points = (bGPDspoint *)MEM_callocN(sizeof(bGPDspoint), "gp_stroke_points");
bGPDspoint *pt_src = &gps->points[0];
bGPDspoint *pt_dst = &gps_temp->points[0];
copy_v3_v3(&pt_dst->x, &pt_src->x);
pt_dst->pressure = avg_pressure;
const float radius = stroke_point_radius_get(gpl, gps_temp);
BKE_gpencil_free_stroke(gps_temp);
pugi::xml_node node_gps = node_gpl.append_child(do_fill || cyclic ? "polygon" : "polyline");
color_string_set(gpl, gps, node_gps, do_fill);
if (is_stroke && !do_fill) {
node_gps.append_attribute("stroke-width").set_value((radius * 2.0f) - gpl->line_change);
}
std::string txt;
for (const int i : IndexRange(gps->totpoints)) {
if (i > 0) {
txt.append(" ");
}
bGPDspoint *pt = &gps->points[i];
const float2 screen_co = gpencil_3D_point_to_2D(&pt->x);
txt.append(std::to_string(screen_co.x) + "," + std::to_string(screen_co.y));
}
node_gps.append_attribute("points").set_value(txt.c_str());
}
/**
* Set color SVG string for stroke
* \param node_gps: Stroke node
* @param do_fill: True if the stroke is only fill
*/
void GpencilExporterSVG::color_string_set(bGPDlayer *gpl,
bGPDstroke *gps,
pugi::xml_node node_gps,
const bool do_fill)
{
const bool round_cap = (gps->caps[0] == GP_STROKE_CAP_ROUND ||
gps->caps[1] == GP_STROKE_CAP_ROUND);
float col[3];
if (do_fill) {
interp_v3_v3v3(col, fill_color_, gpl->tintcolor, gpl->tintcolor[3]);
linearrgb_to_srgb_v3_v3(col, col);
std::string stroke_hex = rgb_to_hexstr(col);
node_gps.append_attribute("fill").set_value(stroke_hex.c_str());
node_gps.append_attribute("stroke").set_value("none");
node_gps.append_attribute("fill-opacity").set_value(fill_color_[3] * gpl->opacity);
}
else {
interp_v3_v3v3(col, stroke_color_, gpl->tintcolor, gpl->tintcolor[3]);
linearrgb_to_srgb_v3_v3(col, col);
std::string stroke_hex = rgb_to_hexstr(col);
node_gps.append_attribute("stroke").set_value(stroke_hex.c_str());
node_gps.append_attribute("stroke-opacity")
.set_value(stroke_color_[3] * stroke_average_opacity_get() * gpl->opacity);
if (gps->totpoints > 1) {
node_gps.append_attribute("fill").set_value("none");
node_gps.append_attribute("stroke-linecap").set_value(round_cap ? "round" : "square");
}
else {
node_gps.append_attribute("fill").set_value(stroke_hex.c_str());
node_gps.append_attribute("fill-opacity").set_value(fill_color_[3] * gpl->opacity);
}
}
}
/**
* Create a SVG rectangle
* \param node: Parent node
* \param x: X location
* \param y: Y location
* \param width: width of the recntagle
* \param height: Height of the rectangle
* \param thickness: Thickness of the line
* \param hexcolor: Color of the line
*/
void GpencilExporterSVG::add_rect(pugi::xml_node node,
float x,
float y,
float width,
float height,
float thickness,
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);
}
}
/**
* Create SVG text
* \param node: Parent node
* \param x: X location
* \param y: Y location
* \param text: Text to include
* \param size: Size of th etext
* \param hexcolor: Color of the text
*/
void GpencilExporterSVG::add_text(pugi::xml_node node,
float x,
float y,
std::string text,
const float size,
std::string hexcolor)
{
pugi::xml_node nodetxt = node.append_child("text");
nodetxt.append_attribute("x").set_value(x);
nodetxt.append_attribute("y").set_value(y);
// nodetxt.append_attribute("font-family").set_value("'system-ui'");
nodetxt.append_attribute("font-size").set_value(size);
nodetxt.append_attribute("fill").set_value(hexcolor.c_str());
nodetxt.text().set(text.c_str());
}
/** Convert a color to Hex value (#FFFFFF). */
std::string GpencilExporterSVG::rgb_to_hexstr(float color[3])
{
uint8_t r = color[0] * 255.0f;
uint8_t g = color[1] * 255.0f;
uint8_t b = color[2] * 255.0f;
char hex_string[20];
sprintf(hex_string, "#%02X%02X%02X", r, g, b);
std::string hexstr = hex_string;
return hexstr;
}
} // namespace blender::io::gpencil

View File

@ -0,0 +1,89 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation
* All rights reserved.
*/
#pragma once
/** \file
* \ingroup bgpencil
*/
#include "BLI_path_util.h"
#include "gpencil_io_export_base.h"
#include "pugixml.hpp"
struct GpencilIOParams;
#define SVG_EXPORTER_NAME "SVG Export for Grease Pencil"
#define SVG_EXPORTER_VERSION "v1.0"
namespace blender::io::gpencil {
class GpencilExporterSVG : public GpencilExporter {
public:
GpencilExporterSVG(const char *filename, const struct GpencilIOParams *iparams);
bool add_newpage();
bool add_body();
bool write();
protected:
static void add_rect(pugi::xml_node node,
float x,
float y,
float width,
float height,
float thickness,
std::string hexcolor);
static void add_text(pugi::xml_node node,
float x,
float y,
std::string text,
const float size,
std::string hexcolor);
private:
/* XML doc. */
pugi::xml_document main_doc_;
/* Main document node. */
pugi::xml_node main_node_;
/** Frame node */
pugi::xml_node frame_node_;
void create_document_header();
void export_gpencil_layers();
void export_stroke_to_path(struct bGPDlayer *gpl,
struct bGPDstroke *gps,
pugi::xml_node node_gpl,
const bool is_fill);
void export_stroke_to_polyline(struct bGPDlayer *gpl,
struct bGPDstroke *gps,
pugi::xml_node node_gpl,
const bool is_stroke,
const bool is_fill);
void color_string_set(struct bGPDlayer *gpl,
struct bGPDstroke *gps,
pugi::xml_node node_gps,
const bool is_fill);
std::string rgb_to_hexstr(float color[3]);
};
} // namespace blender::io::gpencil

View File

@ -0,0 +1,85 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation
* All rights reserved.
*/
/** \file
* \ingroup bgpencil
*/
#include "BLI_math_vector.h"
#include "DNA_material_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "BKE_gpencil.h"
#include "BKE_material.h"
#include "ED_gpencil.h"
#include "gpencil_io_import_base.h"
#include "pugixml.hpp"
namespace blender::io::gpencil {
/* Constructor. */
GpencilImporter::GpencilImporter(const GpencilIOParams *iparams) : GpencilIO(iparams)
{
/* Nothing to do yet */
}
Object *GpencilImporter::create_object()
{
const float *cur = scene_->cursor.location;
ushort local_view_bits = (params_.v3d && params_.v3d->localvd) ? params_.v3d->local_view_uuid :
(ushort)0;
Object *ob_gpencil = ED_gpencil_add_object(params_.C, cur, local_view_bits);
return ob_gpencil;
}
int32_t GpencilImporter::create_material(const char *name, const bool stroke, const bool fill)
{
const float default_stroke_color[4] = {0.0f, 0.0f, 0.0f, 1.0f};
const float default_fill_color[4] = {0.5f, 0.5f, 0.5f, 1.0f};
int32_t mat_index = BKE_gpencil_material_find_index_by_name_prefix(params_.ob, name);
/* Stroke and Fill material. */
if (mat_index == -1) {
int32_t new_idx;
Material *mat_gp = BKE_gpencil_object_material_new(bmain_, params_.ob, name, &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 = params_.ob->totcol - 1;
}
return mat_index;
}
} // namespace blender::io::gpencil

View File

@ -0,0 +1,41 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation
* All rights reserved.
*/
#pragma once
/** \file
* \ingroup bgpencil
*/
#include "gpencil_io_base.h"
namespace blender::io::gpencil {
class GpencilImporter : public GpencilIO {
public:
GpencilImporter(const struct GpencilIOParams *iparams);
virtual bool read() = 0;
protected:
struct Object *create_object();
int32_t create_material(const char *name, const bool stroke, const bool fill);
private:
};
} // namespace blender::io::gpencil

View File

@ -0,0 +1,253 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation
* All rights reserved.
*/
/** \file
* \ingroup bgpencil
*/
#include "BLI_float3.hh"
#include "BLI_math.h"
#include "BLI_span.hh"
#include "DNA_gpencil_types.h"
#include "BKE_gpencil.h"
#include "BKE_gpencil_geom.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
#include "ED_gpencil.h"
#include "gpencil_io.h"
#include "gpencil_io_import_svg.h"
/* Custom flags for NanoSVG. */
#define NANOSVG_ALL_COLOR_KEYWORDS
#define NANOSVG_IMPLEMENTATION
#include "nanosvg/nanosvg.h"
using blender::MutableSpan;
namespace blender::io::gpencil {
/* Constructor. */
GpencilImporterSVG::GpencilImporterSVG(const char *filename, const GpencilIOParams *iparams)
: GpencilImporter(iparams)
{
filename_set(filename);
}
bool GpencilImporterSVG::read()
{
bool result = true;
NSVGimage *svg_data = nullptr;
svg_data = nsvgParseFromFile(filename_, "mm", 96.0f);
if (svg_data == nullptr) {
std::cout << " Could not open SVG.\n ";
return false;
}
/* Create grease pencil object. */
params_.ob = create_object();
if (params_.ob == nullptr) {
std::cout << "Unable to create new object.\n";
if (svg_data) {
nsvgDelete(svg_data);
}
return false;
}
gpd_ = (bGPdata *)params_.ob->data;
/* Grease pencil is rotated 90 degrees in X axis by default. */
float matrix[4][4];
const float3 scale = float3(params_.scale);
unit_m4(matrix);
rotate_m4(matrix, 'X', DEG2RADF(-90.0f));
rescale_m4(matrix, scale);
/* Loop all shapes. */
char prv_id[70] = {"*"};
int prefix = 0;
for (NSVGshape *shape = svg_data->shapes; shape; shape = shape->next) {
char *layer_id = (shape->id_parent[0] == '\0') ? BLI_sprintfN("Layer_%03d", prefix) :
BLI_sprintfN("%s", shape->id_parent);
if (!STREQ(prv_id, layer_id)) {
prefix++;
MEM_freeN(layer_id);
layer_id = (shape->id_parent[0] == '\0') ? BLI_sprintfN("Layer_%03d", prefix) :
BLI_sprintfN("%s", shape->id_parent);
strcpy(prv_id, layer_id);
}
/* Check if the layer exist and create if needed. */
bGPDlayer *gpl = (bGPDlayer *)BLI_findstring(
&gpd_->layers, layer_id, offsetof(bGPDlayer, info));
if (gpl == nullptr) {
gpl = BKE_gpencil_layer_addnew(gpd_, layer_id, true);
/* Disable lights. */
gpl->flag &= ~GP_LAYER_USE_LIGHTS;
}
MEM_freeN(layer_id);
/* Check frame. */
bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, cfra_, GP_GETFRAME_ADD_NEW);
/* Create materials. */
bool is_stroke = (bool)shape->stroke.type;
bool is_fill = (bool)shape->fill.type;
if ((!is_stroke) && (!is_fill)) {
is_stroke = true;
}
/* Create_shape materials. */
const char *const mat_names[] = {"Stroke", "Fill"};
int index = 0;
if ((is_stroke) && (is_fill)) {
index = 0;
is_fill = false;
}
else if ((!is_stroke) && (is_fill)) {
index = 1;
}
int32_t mat_index = create_material(mat_names[index], is_stroke, is_fill);
/* Loop all paths to create the stroke data. */
for (NSVGpath *path = shape->paths; path; path = path->next) {
create_stroke(gpd_, gpf, shape, path, mat_index, matrix);
}
}
/* Free SVG memory. */
nsvgDelete(svg_data);
/* Calculate bounding box and move all points to new origin center. */
float gp_center[3];
BKE_gpencil_centroid_3d(gpd_, gp_center);
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd_->layers) {
LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
for (bGPDspoint &pt : MutableSpan(gps->points, gps->totpoints)) {
sub_v3_v3(&pt.x, gp_center);
}
}
}
}
return result;
}
void GpencilImporterSVG::create_stroke(bGPdata *gpd,
bGPDframe *gpf,
NSVGshape *shape,
NSVGpath *path,
const int32_t mat_index,
const float matrix[4][4])
{
const bool is_stroke = (bool)shape->stroke.type;
const bool is_fill = (bool)shape->fill.type;
const int edges = params_.resolution;
const float step = 1.0f / (float)(edges - 1);
const int totpoints = (path->npts / 3) * params_.resolution;
bGPDstroke *gps = BKE_gpencil_stroke_new(mat_index, totpoints, 1.0f);
BLI_addtail(&gpf->strokes, gps);
if (path->closed == '1') {
gps->flag |= GP_STROKE_CYCLIC;
}
if (is_stroke) {
gps->thickness = shape->strokeWidth * params_.scale;
}
/* Apply Fill vertex color. */
if (is_fill) {
NSVGpaint fill = shape->fill;
convert_color(fill.color, gps->vert_color_fill);
gps->fill_opacity_fac = gps->vert_color_fill[3];
gps->vert_color_fill[3] = 1.0f;
}
int start_index = 0;
for (int i = 0; i < path->npts - 1; i += 3) {
float *p = &path->pts[i * 2];
float a = 0.0f;
for (int v = 0; v < edges; v++) {
bGPDspoint *pt = &gps->points[start_index];
pt->strength = shape->opacity;
pt->pressure = 1.0f;
pt->z = 0.0f;
/* TODO: (antoniov) Can be improved loading curve data instead of loading strokes. */
interp_v2_v2v2v2v2_cubic(&pt->x, &p[0], &p[2], &p[4], &p[6], a);
/* Scale from milimeters. */
mul_v3_fl(&pt->x, 0.001f);
mul_m4_v3(matrix, &pt->x);
/* Apply color to vertex color. */
if (is_fill) {
NSVGpaint fill = shape->fill;
convert_color(fill.color, pt->vert_color);
}
if (is_stroke) {
NSVGpaint stroke = shape->stroke;
convert_color(stroke.color, pt->vert_color);
gps->fill_opacity_fac = pt->vert_color[3];
}
pt->vert_color[3] = 1.0f;
a += step;
start_index++;
}
}
/* Cleanup and recalculate geometry. */
BKE_gpencil_stroke_merge_distance(gpd, gpf, gps, 0.001f, true);
BKE_gpencil_stroke_geometry_update(gpd, gps);
}
/* Unpack internal NanoSVG color. */
static void unpack_nano_color(const unsigned int pack, float r_col[4])
{
unsigned char rgb_u[4];
rgb_u[0] = ((pack) >> 0) & 0xFF;
rgb_u[1] = ((pack) >> 8) & 0xFF;
rgb_u[2] = ((pack) >> 16) & 0xFF;
rgb_u[3] = ((pack) >> 24) & 0xFF;
r_col[0] = (float)rgb_u[0] / 255.0f;
r_col[1] = (float)rgb_u[1] / 255.0f;
r_col[2] = (float)rgb_u[2] / 255.0f;
r_col[3] = (float)rgb_u[3] / 255.0f;
}
void GpencilImporterSVG::convert_color(const int32_t color, float r_linear_rgba[4])
{
float rgba[4];
unpack_nano_color(color, rgba);
srgb_to_linearrgb_v3_v3(r_linear_rgba, rgba);
r_linear_rgba[3] = rgba[3];
}
} // namespace blender::io::gpencil

View File

@ -0,0 +1,56 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation
* All rights reserved.
*/
#pragma once
/** \file
* \ingroup bgpencil
*/
#include "gpencil_io_import_base.h"
struct GpencilIOParams;
struct NSVGshape;
struct NSVGpath;
struct bGPdata;
struct bGPDframe;
#define SVG_IMPORTER_NAME "SVG Import for Grease Pencil"
#define SVG_IMPORTER_VERSION "v1.0"
namespace blender::io::gpencil {
class GpencilImporterSVG : public GpencilImporter {
public:
GpencilImporterSVG(const char *filename, const struct GpencilIOParams *iparams);
bool read();
protected:
private:
void create_stroke(struct bGPdata *gpd_,
struct bGPDframe *gpf,
struct NSVGshape *shape,
struct NSVGpath *path,
const int32_t mat_index,
const float matrix[4][4]);
void convert_color(const int32_t color, float r_linear_rgba[4]);
};
} // namespace blender::io::gpencil

File diff suppressed because it is too large Load Diff

View File

@ -363,4 +363,12 @@ if(WITH_POTRACE)
add_definitions(-DWITH_POTRACE)
endif()
if(WITH_PUGIXML)
add_definitions(-DWITH_PUGIXML)
endif()
if(WITH_HARU)
add_definitions(-DWITH_HARU)
endif()
blender_add_lib(bf_python "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")

View File

@ -65,6 +65,8 @@ static PyStructSequence_Field app_builtopts_info_fields[] = {
{"fluid", NULL},
{"xr_openxr", NULL},
{"potrace", NULL},
{"pugixml", NULL},
{"haru", NULL},
/* Sentinel (this line prevents `clang-format` wrapping into columns). */
{NULL},
};
@ -311,6 +313,18 @@ static PyObject *make_builtopts_info(void)
SetObjIncref(Py_False);
#endif
#ifdef WITH_PUGIXML
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#ifdef WITH_HARU
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#undef SetObjIncref
return builtopts_info;