From 3f47df577d05478a324b6eca80df0a33c838fab8 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Wed, 24 Mar 2021 12:38:08 +1100 Subject: Cleanup: use new BLI_assert_unreachable macro --- source/blender/blenkernel/intern/appdir.c | 4 ++-- source/blender/blenkernel/intern/armature.c | 4 ++-- source/blender/blenkernel/intern/colortools.c | 4 ++-- source/blender/blenkernel/intern/fluid.c | 2 +- source/blender/blenkernel/intern/main.c | 2 +- source/blender/blenkernel/intern/mesh_wrapper.c | 14 +++++++------- source/blender/blenkernel/intern/object.c | 4 ++-- source/blender/blenkernel/intern/paint.c | 2 +- source/blender/editors/util/ed_util.c | 2 +- source/blender/editors/util/select_utils.c | 4 ++-- source/blender/editors/uvedit/uvedit_rip.c | 4 ++-- source/blender/editors/uvedit/uvedit_select.c | 2 +- source/blender/imbuf/intern/thumbs.c | 4 ++-- source/blender/python/bmesh/bmesh_py_types.c | 2 +- source/blender/python/bmesh/bmesh_py_types_customdata.c | 4 ++-- source/blender/python/generic/imbuf_py_api.c | 2 +- source/blender/python/gpu/gpu_py_matrix.c | 4 ++-- source/blender/python/gpu/gpu_py_vertex_buffer.c | 3 ++- source/blender/python/intern/bpy_rna.c | 6 +++--- source/blender/python/mathutils/mathutils_Matrix.c | 5 +++-- source/blender/sequencer/intern/sequencer.c | 2 +- source/blender/windowmanager/gizmo/intern/wm_gizmo_group.c | 8 ++++---- source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c | 2 +- source/blender/windowmanager/intern/wm_event_system.c | 6 +++--- source/blender/windowmanager/intern/wm_files.c | 2 +- source/blender/windowmanager/intern/wm_files_link.c | 2 +- source/blender/windowmanager/intern/wm_operators.c | 6 +++--- source/blender/windowmanager/intern/wm_window.c | 2 +- 28 files changed, 55 insertions(+), 53 deletions(-) diff --git a/source/blender/blenkernel/intern/appdir.c b/source/blender/blenkernel/intern/appdir.c index ff799d9a495..1075a46e72b 100644 --- a/source/blender/blenkernel/intern/appdir.c +++ b/source/blender/blenkernel/intern/appdir.c @@ -664,7 +664,7 @@ bool BKE_appdir_folder_id_ex(const int folder_id, return false; default: - BLI_assert(0); + BLI_assert_unreachable(); break; } @@ -719,7 +719,7 @@ const char *BKE_appdir_folder_id_user_notest(const int folder_id, const char *su get_path_user_ex(path, sizeof(path), "scripts", subfolder, version, check_is_dir); break; default: - BLI_assert(0); + BLI_assert_unreachable(); break; } diff --git a/source/blender/blenkernel/intern/armature.c b/source/blender/blenkernel/intern/armature.c index 92759221f79..a1ebec1d756 100644 --- a/source/blender/blenkernel/intern/armature.c +++ b/source/blender/blenkernel/intern/armature.c @@ -1691,7 +1691,7 @@ void BKE_bone_parent_transform_calc_from_matrices(int bone_flag, break; default: - BLI_assert(false); + BLI_assert_unreachable(); } } /* If removing parent pose rotation: */ @@ -1723,7 +1723,7 @@ void BKE_bone_parent_transform_calc_from_matrices(int bone_flag, break; default: - BLI_assert(false); + BLI_assert_unreachable(); } } diff --git a/source/blender/blenkernel/intern/colortools.c b/source/blender/blenkernel/intern/colortools.c index 44d9bd6b2d2..f30fcc54b23 100644 --- a/source/blender/blenkernel/intern/colortools.c +++ b/source/blender/blenkernel/intern/colortools.c @@ -1374,7 +1374,7 @@ void BKE_histogram_update_sample_line(Histogram *hist, rgba[3] = 1.0f; break; default: - BLI_assert(0); + BLI_assert_unreachable(); } hist->data_luma[i] = IMB_colormanagement_get_luminance(rgba); @@ -1476,7 +1476,7 @@ static void scopes_update_cb(void *__restrict userdata, rgba[3] = 1.0f; break; default: - BLI_assert(0); + BLI_assert_unreachable(); } } else { diff --git a/source/blender/blenkernel/intern/fluid.c b/source/blender/blenkernel/intern/fluid.c index 4c85fda4a37..851d8aae378 100644 --- a/source/blender/blenkernel/intern/fluid.c +++ b/source/blender/blenkernel/intern/fluid.c @@ -936,7 +936,7 @@ static void update_velocities(FluidEffectorSettings *fes, } else { /* Should never reach this block. */ - BLI_assert(false); + BLI_assert_unreachable(); } } else { diff --git a/source/blender/blenkernel/intern/main.c b/source/blender/blenkernel/intern/main.c index d1f34ad8ce9..dc678f248c9 100644 --- a/source/blender/blenkernel/intern/main.c +++ b/source/blender/blenkernel/intern/main.c @@ -182,7 +182,7 @@ void BKE_main_free(Main *mainvar) BKE_id_free_ex(mainvar, id, free_flag, false); break; default: - BLI_assert(0); + BLI_assert_unreachable(); break; } #endif diff --git a/source/blender/blenkernel/intern/mesh_wrapper.c b/source/blender/blenkernel/intern/mesh_wrapper.c index acd272ac305..5df9f7816e3 100644 --- a/source/blender/blenkernel/intern/mesh_wrapper.c +++ b/source/blender/blenkernel/intern/mesh_wrapper.c @@ -148,7 +148,7 @@ bool BKE_mesh_wrapper_minmax(const Mesh *me, float min[3], float max[3]) case ME_WRAPPER_TYPE_MDATA: return BKE_mesh_minmax(me, min, max); } - BLI_assert(0); + BLI_assert_unreachable(); return false; } @@ -189,7 +189,7 @@ void BKE_mesh_wrapper_vert_coords_copy(const Mesh *me, return; } } - BLI_assert(0); + BLI_assert_unreachable(); } void BKE_mesh_wrapper_vert_coords_copy_with_mat4(const Mesh *me, @@ -226,7 +226,7 @@ void BKE_mesh_wrapper_vert_coords_copy_with_mat4(const Mesh *me, return; } } - BLI_assert(0); + BLI_assert_unreachable(); } /** \} */ @@ -243,7 +243,7 @@ int BKE_mesh_wrapper_vert_len(const Mesh *me) case ME_WRAPPER_TYPE_MDATA: return me->totvert; } - BLI_assert(0); + BLI_assert_unreachable(); return -1; } @@ -255,7 +255,7 @@ int BKE_mesh_wrapper_edge_len(const Mesh *me) case ME_WRAPPER_TYPE_MDATA: return me->totedge; } - BLI_assert(0); + BLI_assert_unreachable(); return -1; } @@ -267,7 +267,7 @@ int BKE_mesh_wrapper_loop_len(const Mesh *me) case ME_WRAPPER_TYPE_MDATA: return me->totloop; } - BLI_assert(0); + BLI_assert_unreachable(); return -1; } @@ -279,7 +279,7 @@ int BKE_mesh_wrapper_poly_len(const Mesh *me) case ME_WRAPPER_TYPE_MDATA: return me->totpoly; } - BLI_assert(0); + BLI_assert_unreachable(); return -1; } diff --git a/source/blender/blenkernel/intern/object.c b/source/blender/blenkernel/intern/object.c index 00e99f193a2..b07c4b22c39 100644 --- a/source/blender/blenkernel/intern/object.c +++ b/source/blender/blenkernel/intern/object.c @@ -1858,7 +1858,7 @@ bool BKE_object_data_is_in_editmode(const ID *id) case ID_AR: return ((const bArmature *)id)->edbo != NULL; default: - BLI_assert(0); + BLI_assert_unreachable(); return false; } } @@ -1905,7 +1905,7 @@ char *BKE_object_data_editmode_flush_ptr_get(struct ID *id) return &arm->needs_flush_to_id; } default: - BLI_assert(0); + BLI_assert_unreachable(); return NULL; } return NULL; diff --git a/source/blender/blenkernel/intern/paint.c b/source/blender/blenkernel/intern/paint.c index 2e81b61ad8c..3494630e1fa 100644 --- a/source/blender/blenkernel/intern/paint.c +++ b/source/blender/blenkernel/intern/paint.c @@ -656,7 +656,7 @@ void BKE_paint_runtime_init(const ToolSettings *ts, Paint *paint) paint->runtime.ob_mode = OB_MODE_WEIGHT_GPENCIL; } else { - BLI_assert(0); + BLI_assert_unreachable(); } } diff --git a/source/blender/editors/util/ed_util.c b/source/blender/editors/util/ed_util.c index 9903711834a..da94eef4917 100644 --- a/source/blender/editors/util/ed_util.c +++ b/source/blender/editors/util/ed_util.c @@ -146,7 +146,7 @@ void ED_editors_init(bContext *C) ED_object_wpaintmode_enter_ex(bmain, depsgraph, scene, ob); } else { - BLI_assert(0); + BLI_assert_unreachable(); } } else { diff --git a/source/blender/editors/util/select_utils.c b/source/blender/editors/util/select_utils.c index 85f48e6d397..14a6d751bb1 100644 --- a/source/blender/editors/util/select_utils.c +++ b/source/blender/editors/util/select_utils.c @@ -94,7 +94,7 @@ int ED_select_similar_compare_float(const float delta, const float thresh, const case SIM_CMP_LT: return ((delta - thresh) < FLT_EPSILON); default: - BLI_assert(0); + BLI_assert_unreachable(); return 0; } } @@ -124,7 +124,7 @@ bool ED_select_similar_compare_float_tree(const KDTree_1d *tree, nearest_edge_length = FLT_MAX; break; default: - BLI_assert(0); + BLI_assert_unreachable(); return false; } diff --git a/source/blender/editors/uvedit/uvedit_rip.c b/source/blender/editors/uvedit/uvedit_rip.c index c8aa21191ba..e1b9a287457 100644 --- a/source/blender/editors/uvedit/uvedit_rip.c +++ b/source/blender/editors/uvedit/uvedit_rip.c @@ -169,7 +169,7 @@ static BMLoop *bm_loop_find_other_fan_loop_with_visible_face(BMLoop *l_src, l_other = l_other->prev; } else { - BLI_assert(0); + BLI_assert_unreachable(); } } return l_other; @@ -189,7 +189,7 @@ static BMLoop *bm_vert_step_fan_loop_uv(BMLoop *l, BMEdge **e_step, const int cd l_next = l; } else { - BLI_assert(0); + BLI_assert_unreachable(); return NULL; } diff --git a/source/blender/editors/uvedit/uvedit_select.c b/source/blender/editors/uvedit/uvedit_select.c index f46975c9378..c10e132a4e2 100644 --- a/source/blender/editors/uvedit/uvedit_select.c +++ b/source/blender/editors/uvedit/uvedit_select.c @@ -2213,7 +2213,7 @@ static int uv_mouse_select_loop_generic_multi(bContext *C, flush = uv_select_edgering(sima, scene, obedit, &hit, extend); } else { - BLI_assert(0); + BLI_assert_unreachable(); } if (ts->uv_flag & UV_SYNC_SELECTION) { diff --git a/source/blender/imbuf/intern/thumbs.c b/source/blender/imbuf/intern/thumbs.c index 0d2080b5f0a..61bc185eb8d 100644 --- a/source/blender/imbuf/intern/thumbs.c +++ b/source/blender/imbuf/intern/thumbs.c @@ -393,7 +393,7 @@ static ImBuf *thumb_create_ex(const char *file_path, img = IMB_thumb_load_font(file_path, tsize, tsize); break; default: - BLI_assert(0); /* This should never happen */ + BLI_assert_unreachable(); /* This should never happen */ } } @@ -738,7 +738,7 @@ void IMB_thumb_path_unlock(const char *path) if (thumb_locks.locked_paths) { if (!BLI_gset_remove(thumb_locks.locked_paths, key, MEM_freeN)) { - BLI_assert(0); + BLI_assert_unreachable(); } BLI_condition_notify_all(&thumb_locks.cond); } diff --git a/source/blender/python/bmesh/bmesh_py_types.c b/source/blender/python/bmesh/bmesh_py_types.c index 5fca6f4cec6..563a76ac824 100644 --- a/source/blender/python/bmesh/bmesh_py_types.c +++ b/source/blender/python/bmesh/bmesh_py_types.c @@ -3984,7 +3984,7 @@ PyObject *BPy_BMElem_CreatePyObject(BMesh *bm, BMHeader *ele) case BM_LOOP: return BPy_BMLoop_CreatePyObject(bm, (BMLoop *)ele); default: - BLI_assert(0); + BLI_assert_unreachable(); PyErr_SetString(PyExc_SystemError, "internal error"); return NULL; } diff --git a/source/blender/python/bmesh/bmesh_py_types_customdata.c b/source/blender/python/bmesh/bmesh_py_types_customdata.c index 471a311c411..78c43d18609 100644 --- a/source/blender/python/bmesh/bmesh_py_types_customdata.c +++ b/source/blender/python/bmesh/bmesh_py_types_customdata.c @@ -56,7 +56,7 @@ static CustomData *bpy_bm_customdata_get(BMesh *bm, char htype) return &bm->ldata; } - BLI_assert(0); + BLI_assert_unreachable(); return NULL; } @@ -958,7 +958,7 @@ PyObject *BPy_BMLayerAccess_CreatePyObject(BMesh *bm, const char htype) type = &BPy_BMLayerAccessLoop_Type; break; default: { - BLI_assert(0); + BLI_assert_unreachable(); type = NULL; break; } diff --git a/source/blender/python/generic/imbuf_py_api.c b/source/blender/python/generic/imbuf_py_api.c index 5b4a4fd237e..97a66bc23c0 100644 --- a/source/blender/python/generic/imbuf_py_api.c +++ b/source/blender/python/generic/imbuf_py_api.c @@ -123,7 +123,7 @@ static PyObject *py_imbuf_resize(Py_ImBuf *self, PyObject *args, PyObject *kw) IMB_scaleImBuf(self->ibuf, UNPACK2(size)); } else { - BLI_assert(0); + BLI_assert_unreachable(); } Py_RETURN_NONE; } diff --git a/source/blender/python/gpu/gpu_py_matrix.c b/source/blender/python/gpu/gpu_py_matrix.c index df7c82379b3..b00a13d5be8 100644 --- a/source/blender/python/gpu/gpu_py_matrix.c +++ b/source/blender/python/gpu/gpu_py_matrix.c @@ -201,7 +201,7 @@ static PyObject *pygpu_matrix_stack_context_enter(BPyGPU_MatrixStackContext *sel self->level = GPU_matrix_stack_level_get_projection(); } else { - BLI_assert(0); + BLI_assert_unreachable(); } Py_RETURN_NONE; } @@ -234,7 +234,7 @@ static PyObject *pygpu_matrix_stack_context_exit(BPyGPU_MatrixStackContext *self } } else { - BLI_assert(0); + BLI_assert_unreachable(); } finally: Py_RETURN_NONE; diff --git a/source/blender/python/gpu/gpu_py_vertex_buffer.c b/source/blender/python/gpu/gpu_py_vertex_buffer.c index 111fa114c43..3c7038186b9 100644 --- a/source/blender/python/gpu/gpu_py_vertex_buffer.c +++ b/source/blender/python/gpu/gpu_py_vertex_buffer.c @@ -70,7 +70,8 @@ break; \ } \ default: \ - BLI_assert(0); \ + BLI_assert_unreachable(); \ + break; \ } \ ((void)0) diff --git a/source/blender/python/intern/bpy_rna.c b/source/blender/python/intern/bpy_rna.c index fab73d0f3dc..49ac2662a31 100644 --- a/source/blender/python/intern/bpy_rna.c +++ b/source/blender/python/intern/bpy_rna.c @@ -1345,7 +1345,7 @@ BLI_bitmap *pyrna_set_to_enum_bitmap(const EnumPropertyItem *items, index = (int)ret_convert.as_unsigned; } else { - BLI_assert(0); + BLI_assert_unreachable(); } } BLI_assert(index < bitmap_size); @@ -5550,7 +5550,7 @@ static PyObject *pyprop_array_foreach_getset(BPy_PropertyArrayRNA *self, case PROP_POINTER: case PROP_COLLECTION: /* Should never happen. */ - BLI_assert(false); + BLI_assert_unreachable(); break; } @@ -5595,7 +5595,7 @@ static PyObject *pyprop_array_foreach_getset(BPy_PropertyArrayRNA *self, case PROP_POINTER: case PROP_COLLECTION: /* Should never happen. */ - BLI_assert(false); + BLI_assert_unreachable(); break; } diff --git a/source/blender/python/mathutils/mathutils_Matrix.c b/source/blender/python/mathutils/mathutils_Matrix.c index 016ba462030..161d2f41592 100644 --- a/source/blender/python/mathutils/mathutils_Matrix.c +++ b/source/blender/python/mathutils/mathutils_Matrix.c @@ -1049,7 +1049,8 @@ static void adjoint_matrix_n(float *mat_dst, const float *mat_src, const ushort break; } default: - BLI_assert(0); + BLI_assert_unreachable(); + break; } } @@ -1159,7 +1160,7 @@ static void matrix_invert_safe_internal(const MatrixObject *self, float *r_mat) break; } default: - BLI_assert(0); + BLI_assert_unreachable(); } } diff --git a/source/blender/sequencer/intern/sequencer.c b/source/blender/sequencer/intern/sequencer.c index 4db7350544b..cc11796496c 100644 --- a/source/blender/sequencer/intern/sequencer.c +++ b/source/blender/sequencer/intern/sequencer.c @@ -488,7 +488,7 @@ static Sequence *seq_dupli(const Scene *scene_src, } else { /* sequence type not handled in duplicate! Expect a crash now... */ - BLI_assert(0); + BLI_assert_unreachable(); } /* When using SEQ_DUPE_UNIQUE_NAME, it is mandatory to add new sequences in relevant container diff --git a/source/blender/windowmanager/gizmo/intern/wm_gizmo_group.c b/source/blender/windowmanager/gizmo/intern/wm_gizmo_group.c index 1e77ccd7a1c..32b6a6e6b31 100644 --- a/source/blender/windowmanager/gizmo/intern/wm_gizmo_group.c +++ b/source/blender/windowmanager/gizmo/intern/wm_gizmo_group.c @@ -324,7 +324,7 @@ bool wm_gizmogroup_is_visible_in_drawstep(const wmGizmoGroup *gzgroup, case WM_GIZMOMAP_DRAWSTEP_3D: return (gzgroup->type->flag & WM_GIZMOGROUPTYPE_3D); default: - BLI_assert(0); + BLI_assert_unreachable(); return false; } } @@ -393,7 +393,7 @@ static int gizmo_select_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSE return OPERATOR_FINISHED; } - BLI_assert(0); + BLI_assert_unreachable(); return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH); } @@ -498,7 +498,7 @@ static int gizmo_tweak_modal(bContext *C, wmOperator *op, const wmEvent *event) bool clear_modal = true; if (gz == NULL) { - BLI_assert(0); + BLI_assert_unreachable(); return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH); } @@ -585,7 +585,7 @@ static int gizmo_tweak_invoke(bContext *C, wmOperator *op, const wmEvent *event) if (!gz) { /* wm_handlers_do_intern shouldn't let this happen */ - BLI_assert(0); + BLI_assert_unreachable(); return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH); } diff --git a/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c b/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c index 9b38f010205..45950a32d85 100644 --- a/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c +++ b/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c @@ -941,7 +941,7 @@ bool WM_gizmomap_select_all(bContext *C, wmGizmoMap *gzmap, const int action) changed = wm_gizmomap_deselect_all(gzmap); break; default: - BLI_assert(0); + BLI_assert_unreachable(); break; } diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c index ed1b53e8e20..791aeeb39cd 100644 --- a/source/blender/windowmanager/intern/wm_event_system.c +++ b/source/blender/windowmanager/intern/wm_event_system.c @@ -144,7 +144,7 @@ wmEvent *wm_event_add(wmWindow *win, const wmEvent *event_to_add) wmEvent *WM_event_add_simulate(wmWindow *win, const wmEvent *event_to_add) { if ((G.f & G_FLAG_EVENT_SIMULATE) == 0) { - BLI_assert(0); + BLI_assert_unreachable(); return NULL; } wmEvent *event = wm_event_add(win, event_to_add); @@ -1037,7 +1037,7 @@ static void wm_operator_finished(bContext *C, wmOperator *op, const bool repeat, ED_area_type_hud_clear(wm, NULL); } else { - BLI_assert(0); + BLI_assert_unreachable(); } } } @@ -2885,7 +2885,7 @@ static int wm_handlers_do_intern(bContext *C, wmEvent *event, ListBase *handlers } else { /* Unreachable (handle all types above). */ - BLI_assert(0); + BLI_assert_unreachable(); } if (action & WM_HANDLER_BREAK) { diff --git a/source/blender/windowmanager/intern/wm_files.c b/source/blender/windowmanager/intern/wm_files.c index a9b30f91bee..bbcb0669cce 100644 --- a/source/blender/windowmanager/intern/wm_files.c +++ b/source/blender/windowmanager/intern/wm_files.c @@ -2298,7 +2298,7 @@ static int operator_state_dispatch(bContext *C, wmOperator *op, OperatorDispatch return target.run(C, op); } } - BLI_assert(false); + BLI_assert_unreachable(); return OPERATOR_CANCELLED; } diff --git a/source/blender/windowmanager/intern/wm_files_link.c b/source/blender/windowmanager/intern/wm_files_link.c index bf7cf81f0a9..840debad01b 100644 --- a/source/blender/windowmanager/intern/wm_files_link.c +++ b/source/blender/windowmanager/intern/wm_files_link.c @@ -750,7 +750,7 @@ static void lib_relocate_do_remap(Main *bmain, /* In some cases, new_id might become direct link, remove parent of library in this case. */ if (new_id->lib->parent && (new_id->tag & LIB_TAG_INDIRECT) == 0) { if (do_reload) { - BLI_assert(0); /* Should not happen in 'pure' reload case... */ + BLI_assert_unreachable(); /* Should not happen in 'pure' reload case... */ } new_id->lib->parent = NULL; } diff --git a/source/blender/windowmanager/intern/wm_operators.c b/source/blender/windowmanager/intern/wm_operators.c index e8a41b04d84..84c16999c1b 100644 --- a/source/blender/windowmanager/intern/wm_operators.c +++ b/source/blender/windowmanager/intern/wm_operators.c @@ -1229,7 +1229,7 @@ ID *WM_operator_drop_load_path(struct bContext *C, wmOperator *op, const short i id = (ID *)BKE_image_load_exists_ex(bmain, path, &exists); } else { - BLI_assert(0); + BLI_assert_unreachable(); } if (!id) { @@ -1248,7 +1248,7 @@ ID *WM_operator_drop_load_path(struct bContext *C, wmOperator *op, const short i BLI_path_rel(((Image *)id)->filepath, BKE_main_blendfile_path(bmain)); } else { - BLI_assert(0); + BLI_assert_unreachable(); } } } @@ -1687,7 +1687,7 @@ static uiBlock *wm_block_search_menu(bContext *C, ARegion *region, void *userdat UI_but_func_menu_search(but); } else { - BLI_assert(0); + BLI_assert_unreachable(); } UI_but_flag_enable(but, UI_BUT_ACTIVATE_ON_INIT); diff --git a/source/blender/windowmanager/intern/wm_window.c b/source/blender/windowmanager/intern/wm_window.c index 25232e8e3d5..eb1da15807c 100644 --- a/source/blender/windowmanager/intern/wm_window.c +++ b/source/blender/windowmanager/intern/wm_window.c @@ -2174,7 +2174,7 @@ void WM_window_screen_rect_calc(const wmWindow *win, rcti *r_rect) screen_rect.ymin += height; break; default: - BLI_assert(0); + BLI_assert_unreachable(); break; } } -- cgit v1.2.3 From 865025c343ac38f649b671d74fb6903dc2ec87da Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Wed, 24 Mar 2021 16:43:17 +1100 Subject: Cleanup: remove stdio.h header from MEM_guardedalloc.h This was included for `FILE *` which isn't used in the header. Ref D10799 --- intern/clog/clog.c | 1 + intern/guardedalloc/MEM_guardedalloc.h | 2 -- intern/guardedalloc/intern/leak_detector.cc | 1 + intern/guardedalloc/intern/mallocn_guarded_impl.c | 1 + intern/guardedalloc/intern/mallocn_lockfree_impl.c | 1 + source/blender/blenkernel/intern/keyconfig.c | 1 + source/blender/blenkernel/intern/text_suggestions.c | 1 + source/blender/blenkernel/intern/undo_system.c | 1 + source/blender/blenkernel/intern/workspace.c | 1 + source/blender/blenlib/intern/BLI_args.c | 1 + source/blender/blenlib/intern/BLI_dynstr.c | 1 + source/blender/blenlib/intern/edgehash.c | 1 + source/blender/blenlib/intern/string.c | 1 + source/blender/editors/interface/interface_style.c | 1 + source/blender/editors/interface/interface_template_search_menu.c | 1 + source/blender/gpu/intern/gpu_material_library.c | 1 + source/blender/gpu/intern/gpu_node_graph.c | 1 + source/blender/imbuf/intern/thumbs_blend.c | 1 + source/blender/modifiers/intern/MOD_subsurf.c | 1 + source/blender/windowmanager/gizmo/intern/wm_gizmo_group_type.c | 2 ++ source/blender/windowmanager/gizmo/intern/wm_gizmo_type.c | 2 ++ source/blender/windowmanager/intern/wm_menu_type.c | 2 ++ source/blender/windowmanager/intern/wm_uilist_type.c | 2 ++ 23 files changed, 26 insertions(+), 2 deletions(-) diff --git a/intern/clog/clog.c b/intern/clog/clog.c index 391b71d77de..01d1c0a1770 100644 --- a/intern/clog/clog.c +++ b/intern/clog/clog.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include diff --git a/intern/guardedalloc/MEM_guardedalloc.h b/intern/guardedalloc/MEM_guardedalloc.h index df5593ba484..3d51c04f929 100644 --- a/intern/guardedalloc/MEM_guardedalloc.h +++ b/intern/guardedalloc/MEM_guardedalloc.h @@ -49,8 +49,6 @@ #ifndef __MEM_GUARDEDALLOC_H__ #define __MEM_GUARDEDALLOC_H__ -#include /* needed for FILE* */ - /* Needed for uintptr_t and attributes, exception, don't use BLI anywhere else in `MEM_*` */ #include "../../source/blender/blenlib/BLI_compiler_attrs.h" #include "../../source/blender/blenlib/BLI_sys_types.h" diff --git a/intern/guardedalloc/intern/leak_detector.cc b/intern/guardedalloc/intern/leak_detector.cc index fb8d4e72cac..3ce6996649e 100644 --- a/intern/guardedalloc/intern/leak_detector.cc +++ b/intern/guardedalloc/intern/leak_detector.cc @@ -19,6 +19,7 @@ */ #include +#include /* Needed for `printf` on WIN32/APPLE. */ #include "MEM_guardedalloc.h" #include "mallocn_intern.h" diff --git a/intern/guardedalloc/intern/mallocn_guarded_impl.c b/intern/guardedalloc/intern/mallocn_guarded_impl.c index 6a0d104d47d..a7c3dc0951e 100644 --- a/intern/guardedalloc/intern/mallocn_guarded_impl.c +++ b/intern/guardedalloc/intern/mallocn_guarded_impl.c @@ -25,6 +25,7 @@ #include #include /* offsetof */ +#include /* printf */ #include #include /* memcpy */ #include diff --git a/intern/guardedalloc/intern/mallocn_lockfree_impl.c b/intern/guardedalloc/intern/mallocn_lockfree_impl.c index 8f5c9cf85a9..a843086a1f1 100644 --- a/intern/guardedalloc/intern/mallocn_lockfree_impl.c +++ b/intern/guardedalloc/intern/mallocn_lockfree_impl.c @@ -21,6 +21,7 @@ */ #include +#include /* printf */ #include #include /* memcpy */ #include diff --git a/source/blender/blenkernel/intern/keyconfig.c b/source/blender/blenkernel/intern/keyconfig.c index ada5fc5b6aa..552760c9b34 100644 --- a/source/blender/blenkernel/intern/keyconfig.c +++ b/source/blender/blenkernel/intern/keyconfig.c @@ -19,6 +19,7 @@ */ #include +#include #include #include "RNA_types.h" diff --git a/source/blender/blenkernel/intern/text_suggestions.c b/source/blender/blenkernel/intern/text_suggestions.c index 6df1aff722b..d717b88e8ca 100644 --- a/source/blender/blenkernel/intern/text_suggestions.c +++ b/source/blender/blenkernel/intern/text_suggestions.c @@ -22,6 +22,7 @@ */ #include +#include #include #include diff --git a/source/blender/blenkernel/intern/undo_system.c b/source/blender/blenkernel/intern/undo_system.c index 52f0fe3f5a2..377802f1af7 100644 --- a/source/blender/blenkernel/intern/undo_system.c +++ b/source/blender/blenkernel/intern/undo_system.c @@ -20,6 +20,7 @@ * Used by ED_undo.h, internal implementation. */ +#include #include #include "CLG_log.h" diff --git a/source/blender/blenkernel/intern/workspace.c b/source/blender/blenkernel/intern/workspace.c index 481d190952f..be67b2370e3 100644 --- a/source/blender/blenkernel/intern/workspace.c +++ b/source/blender/blenkernel/intern/workspace.c @@ -18,6 +18,7 @@ * \ingroup bke */ +#include #include #include diff --git a/source/blender/blenlib/intern/BLI_args.c b/source/blender/blenlib/intern/BLI_args.c index 07ab4f407f2..70a51982925 100644 --- a/source/blender/blenlib/intern/BLI_args.c +++ b/source/blender/blenlib/intern/BLI_args.c @@ -23,6 +23,7 @@ */ #include /* for tolower */ +#include #include #include "MEM_guardedalloc.h" diff --git a/source/blender/blenlib/intern/BLI_dynstr.c b/source/blender/blenlib/intern/BLI_dynstr.c index 3e80f791fa5..7b25fecfa45 100644 --- a/source/blender/blenlib/intern/BLI_dynstr.c +++ b/source/blender/blenlib/intern/BLI_dynstr.c @@ -22,6 +22,7 @@ * \ingroup bli */ +#include #include /* malloc */ #include diff --git a/source/blender/blenlib/intern/edgehash.c b/source/blender/blenlib/intern/edgehash.c index 05ee02ad869..b8bf535a3b4 100644 --- a/source/blender/blenlib/intern/edgehash.c +++ b/source/blender/blenlib/intern/edgehash.c @@ -24,6 +24,7 @@ */ #include +#include #include #include diff --git a/source/blender/blenlib/intern/string.c b/source/blender/blenlib/intern/string.c index 3bfedd6f586..ccc11af9f2b 100644 --- a/source/blender/blenlib/intern/string.c +++ b/source/blender/blenlib/intern/string.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include diff --git a/source/blender/editors/interface/interface_style.c b/source/blender/editors/interface/interface_style.c index eaefc2c3736..ad0c523a594 100644 --- a/source/blender/editors/interface/interface_style.c +++ b/source/blender/editors/interface/interface_style.c @@ -23,6 +23,7 @@ #include #include +#include #include #include diff --git a/source/blender/editors/interface/interface_template_search_menu.c b/source/blender/editors/interface/interface_template_search_menu.c index 74668b2f3a3..ff42d434f29 100644 --- a/source/blender/editors/interface/interface_template_search_menu.c +++ b/source/blender/editors/interface/interface_template_search_menu.c @@ -21,6 +21,7 @@ * Accessed via the #WM_OT_search_menu operator. */ +#include #include #include "MEM_guardedalloc.h" diff --git a/source/blender/gpu/intern/gpu_material_library.c b/source/blender/gpu/intern/gpu_material_library.c index 64cd375d466..175facc0a8d 100644 --- a/source/blender/gpu/intern/gpu_material_library.c +++ b/source/blender/gpu/intern/gpu_material_library.c @@ -23,6 +23,7 @@ * GPU material library parsing and code generation. */ +#include #include #include "MEM_guardedalloc.h" diff --git a/source/blender/gpu/intern/gpu_node_graph.c b/source/blender/gpu/intern/gpu_node_graph.c index bf91a5bbb4d..b220c60e979 100644 --- a/source/blender/gpu/intern/gpu_node_graph.c +++ b/source/blender/gpu/intern/gpu_node_graph.c @@ -23,6 +23,7 @@ * Intermediate node graph for generating GLSL shaders. */ +#include #include #include "MEM_guardedalloc.h" diff --git a/source/blender/imbuf/intern/thumbs_blend.c b/source/blender/imbuf/intern/thumbs_blend.c index 106e4618847..b7b31b3e56a 100644 --- a/source/blender/imbuf/intern/thumbs_blend.c +++ b/source/blender/imbuf/intern/thumbs_blend.c @@ -18,6 +18,7 @@ * \ingroup imbuf */ +#include #include #include diff --git a/source/blender/modifiers/intern/MOD_subsurf.c b/source/blender/modifiers/intern/MOD_subsurf.c index d089894a6e9..c3611488f5f 100644 --- a/source/blender/modifiers/intern/MOD_subsurf.c +++ b/source/blender/modifiers/intern/MOD_subsurf.c @@ -22,6 +22,7 @@ */ #include +#include #include #include "MEM_guardedalloc.h" diff --git a/source/blender/windowmanager/gizmo/intern/wm_gizmo_group_type.c b/source/blender/windowmanager/gizmo/intern/wm_gizmo_group_type.c index a9e24867351..ab5a265547d 100644 --- a/source/blender/windowmanager/gizmo/intern/wm_gizmo_group_type.c +++ b/source/blender/windowmanager/gizmo/intern/wm_gizmo_group_type.c @@ -18,6 +18,8 @@ * \ingroup wm */ +#include + #include "BLI_ghash.h" #include "BLI_utildefines.h" diff --git a/source/blender/windowmanager/gizmo/intern/wm_gizmo_type.c b/source/blender/windowmanager/gizmo/intern/wm_gizmo_type.c index efd7a13d02a..185854b1ca0 100644 --- a/source/blender/windowmanager/gizmo/intern/wm_gizmo_type.c +++ b/source/blender/windowmanager/gizmo/intern/wm_gizmo_type.c @@ -18,6 +18,8 @@ * \ingroup wm */ +#include + #include "BLI_ghash.h" #include "BLI_listbase.h" #include "BLI_utildefines.h" diff --git a/source/blender/windowmanager/intern/wm_menu_type.c b/source/blender/windowmanager/intern/wm_menu_type.c index 0c52e636e4d..bcaa2243f9f 100644 --- a/source/blender/windowmanager/intern/wm_menu_type.c +++ b/source/blender/windowmanager/intern/wm_menu_type.c @@ -20,6 +20,8 @@ * Menu Registry. */ +#include + #include "BLI_sys_types.h" #include "DNA_windowmanager_types.h" diff --git a/source/blender/windowmanager/intern/wm_uilist_type.c b/source/blender/windowmanager/intern/wm_uilist_type.c index ef6d855cb36..45c14c0bbe9 100644 --- a/source/blender/windowmanager/intern/wm_uilist_type.c +++ b/source/blender/windowmanager/intern/wm_uilist_type.c @@ -20,6 +20,8 @@ * UI List Registry. */ +#include + #include "BLI_sys_types.h" #include "DNA_windowmanager_types.h" -- cgit v1.2.3 From 6b18678e34bfbe2e95430fa7491ed1d3fad4ff3a Mon Sep 17 00:00:00 2001 From: Philipp Oeser Date: Tue, 23 Mar 2021 16:26:15 +0100 Subject: Fix T86314: materials not updated correctly after collada import Make sure we have valid links and link pointers using 'ntreeUpdateTree()'. Maniphest Tasks: T86314 Differential Revision: https://developer.blender.org/D10793 --- source/blender/io/collada/Materials.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/blender/io/collada/Materials.cpp b/source/blender/io/collada/Materials.cpp index 644ecc18e9b..6ba31599fcd 100644 --- a/source/blender/io/collada/Materials.cpp +++ b/source/blender/io/collada/Materials.cpp @@ -25,6 +25,8 @@ MaterialNode::MaterialNode(bContext *C, Material *ma, KeyImageMap &key_image_map shader_node = add_node(SH_NODE_BSDF_PRINCIPLED, 0, 300, ""); output_node = add_node(SH_NODE_OUTPUT_MATERIAL, 300, 300, ""); add_link(shader_node, 0, output_node, 0); + + ntreeUpdateTree(CTX_data_main(C), ntree); } } @@ -59,6 +61,8 @@ MaterialNode::MaterialNode(bContext *C, shader_node = add_node(SH_NODE_BSDF_PRINCIPLED, 0, 300, ""); output_node = add_node(SH_NODE_OUTPUT_MATERIAL, 300, 300, ""); add_link(shader_node, 0, output_node, 0); + + ntreeUpdateTree(CTX_data_main(C), ntree); #endif } -- cgit v1.2.3 From 42198e9eb03bdc5494c3717c6b1643539ef06231 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dietrich?= Date: Wed, 24 Mar 2021 10:36:31 +0100 Subject: Fix T86601: Cycles accumulates displacement when transforming an Object In order to update the BVH when only the transformations are changing, we would tag the Object's Geometry as modified. However, when displacement is used, and the vertices were not themselves modified, this would cause us to redo the displacement on already displaced vertices. To fix this, use a specific update flag for detecting and notifying that transformations were modified. Regression caused by rBbbe6d44928235cd4a5cfbeaf1a1de78ed861bb92. --- intern/cycles/render/geometry.cpp | 9 ++++++--- intern/cycles/render/geometry.h | 2 ++ intern/cycles/render/object.cpp | 15 +++++---------- intern/cycles/render/object.h | 1 + 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/intern/cycles/render/geometry.cpp b/intern/cycles/render/geometry.cpp index e9a70e4b3fd..60a6f598ed2 100644 --- a/intern/cycles/render/geometry.cpp +++ b/intern/cycles/render/geometry.cpp @@ -1917,9 +1917,12 @@ void GeometryManager::device_update(Device *device, } } - /* update the bvh even when there is no geometry so the kernel bvh data is still valid, - * especially when removing all of the objects during interactive renders */ - bool need_update_scene_bvh = (scene->bvh == nullptr); + /* Update the BVH even when there is no geometry so the kernel's BVH data is still valid, + * especially when removing all of the objects during interactive renders. + * Also update the BVH if the transformations change, we cannot rely on tagging the Geometry + * as modified in this case, as we may accumulate displacement if the vertices do not also + * change. */ + bool need_update_scene_bvh = (scene->bvh == nullptr || (update_flags & TRANSFORM_MODIFIED) != 0); { scoped_callback_timer timer([scene](double time) { if (scene->update_stats) { diff --git a/intern/cycles/render/geometry.h b/intern/cycles/render/geometry.h index fe30f3a807c..abdd851a089 100644 --- a/intern/cycles/render/geometry.h +++ b/intern/cycles/render/geometry.h @@ -189,6 +189,8 @@ class GeometryManager { GEOMETRY_ADDED = MESH_ADDED | HAIR_ADDED, GEOMETRY_REMOVED = MESH_REMOVED | HAIR_REMOVED, + TRANSFORM_MODIFIED = (1 << 10), + /* tag everything in the manager for an update */ UPDATE_ALL = ~0u, diff --git a/intern/cycles/render/object.cpp b/intern/cycles/render/object.cpp index e71d7d4a3eb..52f63685aeb 100644 --- a/intern/cycles/render/object.cpp +++ b/intern/cycles/render/object.cpp @@ -221,16 +221,7 @@ void Object::tag_update(Scene *scene) if (geometry) { if (tfm_is_modified()) { - /* tag the geometry as modified so the BVH is updated, but do not tag everything as modified - */ - if (geometry->is_mesh() || geometry->is_volume()) { - Mesh *mesh = static_cast(geometry); - mesh->tag_verts_modified(); - } - else if (geometry->is_hair()) { - Hair *hair = static_cast(geometry); - hair->tag_curve_keys_modified(); - } + flag |= ObjectManager::TRANSFORM_MODIFIED; } foreach (Node *node, geometry->get_used_shaders()) { @@ -923,6 +914,10 @@ void ObjectManager::tag_update(Scene *scene, uint32_t flag) geometry_flag |= (GeometryManager::GEOMETRY_ADDED | GeometryManager::GEOMETRY_REMOVED); } + if ((flag & TRANSFORM_MODIFIED) != 0) { + geometry_flag |= GeometryManager::TRANSFORM_MODIFIED; + } + scene->geometry_manager->tag_update(scene, geometry_flag); } diff --git a/intern/cycles/render/object.h b/intern/cycles/render/object.h index cf1b9ca510a..23682270fd1 100644 --- a/intern/cycles/render/object.h +++ b/intern/cycles/render/object.h @@ -133,6 +133,7 @@ class ObjectManager { OBJECT_REMOVED = (1 << 4), OBJECT_MODIFIED = (1 << 5), HOLDOUT_MODIFIED = (1 << 6), + TRANSFORM_MODIFIED = (1 << 7), /* tag everything in the manager for an update */ UPDATE_ALL = ~0u, -- cgit v1.2.3 From 665d8c0e349d0ec917bc28e53236095ea920f656 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dietrich?= Date: Wed, 24 Mar 2021 10:38:46 +0100 Subject: Cleanup: unused variable --- intern/cycles/render/geometry.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/intern/cycles/render/geometry.cpp b/intern/cycles/render/geometry.cpp index 60a6f598ed2..124a41db21e 100644 --- a/intern/cycles/render/geometry.cpp +++ b/intern/cycles/render/geometry.cpp @@ -1964,7 +1964,6 @@ void GeometryManager::device_update(Device *device, scene->update_stats->geometry.times.add_entry({"device_update (compute bounds)", time}); } }); - vector volume_objects; foreach (Object *object, scene->objects) { object->compute_bounds(motion_blur); } -- cgit v1.2.3 From 715c7462969e6075859f44a64bb4eb68d056345b Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Wed, 24 Mar 2021 11:00:31 +0100 Subject: Cleanup: clang-tidy errors. --- intern/guardedalloc/intern/leak_detector.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intern/guardedalloc/intern/leak_detector.cc b/intern/guardedalloc/intern/leak_detector.cc index 3ce6996649e..03d54f2e776 100644 --- a/intern/guardedalloc/intern/leak_detector.cc +++ b/intern/guardedalloc/intern/leak_detector.cc @@ -18,8 +18,8 @@ * \ingroup MEM */ +#include /* Needed for `printf` on WIN32/APPLE. */ #include -#include /* Needed for `printf` on WIN32/APPLE. */ #include "MEM_guardedalloc.h" #include "mallocn_intern.h" -- cgit v1.2.3 From 26b45448abf05dc709678cb821201b0721a51d76 Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Wed, 24 Mar 2021 11:56:58 +0100 Subject: Fix: Memory Leak When Using Compositor. When using the compositor inlined static vectors could allocate memory. This memory wasn't freed. This patch would make them inline again. --- source/blender/compositor/intern/COM_WorkScheduler.cc | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/source/blender/compositor/intern/COM_WorkScheduler.cc b/source/blender/compositor/intern/COM_WorkScheduler.cc index 2bc3ff936b1..56956b9d097 100644 --- a/source/blender/compositor/intern/COM_WorkScheduler.cc +++ b/source/blender/compositor/intern/COM_WorkScheduler.cc @@ -284,12 +284,13 @@ static void opencl_deinitialize() { /* Deinitialize OpenCL GPU's. */ if (g_work_scheduler.opencl.initialized) { - Device *device; while (!g_work_scheduler.opencl.devices.is_empty()) { - device = g_work_scheduler.opencl.devices.pop_last(); + Device *device = g_work_scheduler.opencl.devices.pop_last(); device->deinitialize(); delete device; } + g_work_scheduler.opencl.devices.clear_and_make_inline(); + if (g_work_scheduler.opencl.program) { clReleaseProgram(g_work_scheduler.opencl.program); g_work_scheduler.opencl.program = nullptr; @@ -397,12 +398,13 @@ static void threading_model_queue_deinitialize() { /* deinitialize CPU threads */ if (g_work_scheduler.queue.initialized) { - Device *device; while (!g_work_scheduler.queue.devices.is_empty()) { - device = g_work_scheduler.queue.devices.pop_last(); + Device *device = g_work_scheduler.queue.devices.pop_last(); device->deinitialize(); delete device; } + g_work_scheduler.queue.devices.clear_and_make_inline(); + BLI_thread_local_delete(g_thread_device); g_work_scheduler.queue.initialized = false; } -- cgit v1.2.3 From c0a4c8c3fa68a7343a8444aaf4948f309adec4d6 Mon Sep 17 00:00:00 2001 From: Dirk Date: Wed, 24 Mar 2021 12:53:00 +0100 Subject: Fix T86891: only sort query results for shortest string if there is a query Differential Revision: https://developer.blender.org/D10802 --- source/blender/blenlib/intern/string_search.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/blender/blenlib/intern/string_search.cc b/source/blender/blenlib/intern/string_search.cc index 44baff1f5e3..25a13674932 100644 --- a/source/blender/blenlib/intern/string_search.cc +++ b/source/blender/blenlib/intern/string_search.cc @@ -432,9 +432,11 @@ int BLI_string_search_query(StringSearch *search, const char *query, void ***r_d { using namespace blender; + const StringRef query_str = query; + LinearAllocator<> allocator; Vector query_words; - string_search::extract_normalized_words(query, allocator, query_words); + string_search::extract_normalized_words(query_str, allocator, query_words); /* Compute score of every result. */ MultiValueMap result_indices_by_score; @@ -457,7 +459,7 @@ int BLI_string_search_query(StringSearch *search, const char *query, void ***r_d Vector sorted_result_indices; for (const int score : found_scores) { MutableSpan indices = result_indices_by_score.lookup(score); - if (score == found_scores[0]) { + if (score == found_scores[0] && !query_str.is_empty()) { /* Sort items with best score by length. Shorter items are more likely the ones you are * looking for. This also ensures that exact matches will be at the top, even if the query is * a substring of another item. */ -- cgit v1.2.3 From 9b8262021b782361d82cd0e1f0f1e5711036c580 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Wed, 24 Mar 2021 13:27:47 +0100 Subject: Geometry Nodes: add versioning for attribute fill The attribute domain of old nodes should be set to `ATTR_DOMAIN_AUTO`. --- source/blender/blenkernel/BKE_blender_version.h | 2 +- source/blender/blenloader/intern/versioning_290.c | 27 +++++++++++++++++------ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index eb5f910f555..c9585430ae2 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -39,7 +39,7 @@ extern "C" { /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 13 +#define BLENDER_FILE_SUBVERSION 14 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and show a warning if the file diff --git a/source/blender/blenloader/intern/versioning_290.c b/source/blender/blenloader/intern/versioning_290.c index 00f4c49fda9..31e4b659c2f 100644 --- a/source/blender/blenloader/intern/versioning_290.c +++ b/source/blender/blenloader/intern/versioning_290.c @@ -52,6 +52,7 @@ #include "BKE_animsys.h" #include "BKE_armature.h" +#include "BKE_attribute.h" #include "BKE_collection.h" #include "BKE_colortools.h" #include "BKE_cryptomatte.h" @@ -1905,6 +1906,25 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain) } } + if (!MAIN_VERSION_ATLEAST(bmain, 293, 14)) { + if (!DNA_struct_elem_find(fd->filesdna, "Light", "float", "diff_fac")) { + LISTBASE_FOREACH (Light *, light, &bmain->lights) { + light->diff_fac = 1.0f; + light->volume_fac = 1.0f; + } + } + + LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) { + if (ntree->type == NTREE_GEOMETRY) { + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { + if (node->type == GEO_NODE_ATTRIBUTE_FILL) { + node->custom2 = ATTR_DOMAIN_AUTO; + } + } + } + } + } + /** * Versioning code until next subversion bump goes here. * @@ -1916,12 +1936,5 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain) */ { /* Keep this block, even when empty. */ - - if (!DNA_struct_elem_find(fd->filesdna, "Light", "float", "diff_fac")) { - LISTBASE_FOREACH (Light *, light, &bmain->lights) { - light->diff_fac = 1.0f; - light->volume_fac = 1.0f; - } - } } } -- cgit v1.2.3 From 781f41f633fc61033423ebae657c0495ab9d131b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dietrich?= Date: Wed, 24 Mar 2021 14:18:05 +0100 Subject: Alembic procedural: deduplicate cached data accross frames Currently the procedural will add an entry to the cache for every frame even if the data only changes seldomly. This means that in some cases we will have duplicate data accross frames. The cached data is now stored separately from the time information, and an index is used to retrieve it based on time. This decoupling allows for multiple frames to point to the same data. To check if two arrays are the same, we compute their keys using the Alembic library's routines (which is based on murmur3), and tell the cache to reuse the last data if the keys match. This can drastically reduce memory usage at the cost of more processing time, although processing time is only increased if the topology may change. --- intern/cycles/render/alembic.cpp | 77 ++++++++++++++++++++++++++-------------- intern/cycles/render/alembic.h | 69 ++++++++++++++++++++++++++--------- 2 files changed, 104 insertions(+), 42 deletions(-) diff --git a/intern/cycles/render/alembic.cpp b/intern/cycles/render/alembic.cpp index e4f0690c401..1336f81896a 100644 --- a/intern/cycles/render/alembic.cpp +++ b/intern/cycles/render/alembic.cpp @@ -396,6 +396,10 @@ static void add_uvs(AlembicProcedural *proc, ccl::set times = get_relevant_sample_times(proc, time_sampling, uvs.getNumSamples()); + /* Keys used to determine if the UVs do actually change over time. */ + ArraySample::Key previous_indices_key; + ArraySample::Key previous_values_key; + foreach (chrono_t time, times) { if (progress.get_cancel()) { return; @@ -422,21 +426,32 @@ static void add_uvs(AlembicProcedural *proc, float2 *data_float2 = reinterpret_cast(data.data()); - const unsigned int *indices = uvsample.getIndices()->get(); - const V2f *values = uvsample.getVals()->get(); + const ArraySample::Key indices_key = uvsample.getIndices()->getKey(); + const ArraySample::Key values_key = uvsample.getVals()->getKey(); - for (const int3 &loop : *triangles_loops) { - unsigned int v0 = indices[loop.x]; - unsigned int v1 = indices[loop.y]; - unsigned int v2 = indices[loop.z]; + if (indices_key == previous_indices_key && values_key == previous_values_key) { + attr.data.reuse_data_for_last_time(time); + } + else { + const unsigned int *indices = uvsample.getIndices()->get(); + const V2f *values = uvsample.getVals()->get(); + + for (const int3 &loop : *triangles_loops) { + unsigned int v0 = indices[loop.x]; + unsigned int v1 = indices[loop.y]; + unsigned int v2 = indices[loop.z]; + + data_float2[0] = make_float2(values[v0][0], values[v0][1]); + data_float2[1] = make_float2(values[v1][0], values[v1][1]); + data_float2[2] = make_float2(values[v2][0], values[v2][1]); + data_float2 += 3; + } - data_float2[0] = make_float2(values[v0][0], values[v0][1]); - data_float2[1] = make_float2(values[v1][0], values[v1][1]); - data_float2[2] = make_float2(values[v2][0], values[v2][1]); - data_float2 += 3; + attr.data.add_data(data, time); } - attr.data.add_data(data, time); + previous_indices_key = indices_key; + previous_values_key = values_key; } } @@ -736,6 +751,11 @@ void AlembicObject::load_all_data(AlembicProcedural *proc, ccl::set times = get_relevant_sample_times( proc, *time_sampling, schema.getNumSamples()); + /* Key used to determine if the triangles change over time, if the key is the same as the + * last one, we can avoid creating a new entry in the cache and simply point to the last + * frame. */ + ArraySample::Key previous_key; + /* read topology */ foreach (chrono_t time, times) { if (progress.get_cancel()) { @@ -747,22 +767,27 @@ void AlembicObject::load_all_data(AlembicProcedural *proc, add_positions(sample.getPositions(), time, cached_data); - /* Only copy triangles for other frames if the topology is changing over time as well. - * - * TODO(@kevindietrich): even for dynamic simulations, this is a waste of memory and - * processing time if only the positions are changing in a subsequence of frames but we - * cannot optimize in this current system if the attributes are changing over time as well, - * as we need valid data for each time point. This can be solved by using reference counting - * on the ccl::array and simply share the array across frames. */ + /* Only copy triangles for other frames if the topology is changing over time as well. */ if (schema.getTopologyVariance() != kHomogenousTopology || cached_data.triangles.size() == 0) { - /* start by reading the face sets (per face shader), as we directly split polygons to - * triangles - */ - array polygon_to_shader; - read_face_sets(schema, polygon_to_shader, iss); - - add_triangles( - sample.getFaceCounts(), sample.getFaceIndices(), time, cached_data, polygon_to_shader); + const ArraySample::Key key = sample.getFaceIndices()->getKey(); + + if (key == previous_key) { + cached_data.triangles.reuse_data_for_last_time(time); + cached_data.triangles_loops.reuse_data_for_last_time(time); + cached_data.shader.reuse_data_for_last_time(time); + } + else { + /* start by reading the face sets (per face shader), as we directly split polygons to + * triangles + */ + array polygon_to_shader; + read_face_sets(schema, polygon_to_shader, iss); + + add_triangles( + sample.getFaceCounts(), sample.getFaceIndices(), time, cached_data, polygon_to_shader); + } + + previous_key = key; } if (normals.valid()) { diff --git a/intern/cycles/render/alembic.h b/intern/cycles/render/alembic.h index 0203475571e..d0c5856a353 100644 --- a/intern/cycles/render/alembic.h +++ b/intern/cycles/render/alembic.h @@ -128,12 +128,25 @@ template class CacheLookupResult { * The data is supposed to be stored in chronological order, and is looked up using the current * animation time in seconds using the TimeSampling from the Alembic property. */ template class DataStore { - struct DataTimePair { + /* Holds information to map a cache entry for a given time to an index into the data array. */ + struct TimeIndexPair { + /* Frame time for this entry. */ double time = 0; - T data{}; + /* Frame time for the data pointed to by `index`. */ + double source_time = 0; + /* Index into the data array. */ + size_t index = 0; }; - vector data{}; + /* This is the actual data that is stored. We deduplicate data across frames to avoid storing + * values if they have not changed yet (e.g. the triangles for a building before fracturing, or a + * fluid simulation before a break or splash) */ + vector data{}; + + /* This is used to map they entry for a given time to an index into the data array, multiple + * frames can point to the same index. */ + vector index_data_map{}; + Alembic::AbcCoreAbstract::TimeSampling time_sampling{}; double last_loaded_time = std::numeric_limits::max(); @@ -157,17 +170,21 @@ template class DataStore { return CacheLookupResult::no_data_found_for_time(); } - std::pair index_pair; - index_pair = time_sampling.getNearIndex(time, data.size()); - DataTimePair &data_pair = data[index_pair.first]; + const TimeIndexPair &index = get_index_for_time(time); + + if (index.index == -1ul) { + return CacheLookupResult::no_data_found_for_time(); + } - if (last_loaded_time == data_pair.time) { + if (last_loaded_time == index.time || last_loaded_time == index.source_time) { return CacheLookupResult::already_loaded(); } - last_loaded_time = data_pair.time; + last_loaded_time = index.source_time; - return CacheLookupResult::new_data(&data_pair.data); + assert(index.index < data.size()); + + return CacheLookupResult::new_data(&data[index.index]); } /* get the data for the specified time, but do not check if the data was already loaded for this @@ -178,22 +195,34 @@ template class DataStore { return CacheLookupResult::no_data_found_for_time(); } - std::pair index_pair; - index_pair = time_sampling.getNearIndex(time, data.size()); - DataTimePair &data_pair = data[index_pair.first]; - return CacheLookupResult::new_data(&data_pair.data); + const TimeIndexPair &index = get_index_for_time(time); + + if (index.index == -1ul) { + return CacheLookupResult::no_data_found_for_time(); + } + + assert(index.index < data.size()); + + return CacheLookupResult::new_data(&data[index.index]); } void add_data(T &data_, double time) { + index_data_map.push_back({time, time, data.size()}); + if constexpr (is_array::value) { data.emplace_back(); - data.back().data.steal_data(data_); - data.back().time = time; + data.back().steal_data(data_); return; } - data.push_back({time, data_}); + data.push_back(data_); + } + + void reuse_data_for_last_time(double time) + { + const TimeIndexPair &data_index = index_data_map.back(); + index_data_map.push_back({time, data_index.source_time, data_index.index}); } bool is_constant() const @@ -232,6 +261,14 @@ template class DataStore { T value = result.get_data(); node->set(*socket, value); } + + private: + const TimeIndexPair &get_index_for_time(double time) const + { + std::pair index_pair; + index_pair = time_sampling.getNearIndex(time, index_data_map.size()); + return index_data_map[index_pair.first]; + } }; /* Actual cache for the stored data. -- cgit v1.2.3 From d37deb19bebffcd2abae6c409a2c135adf08f0cd Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Wed, 24 Mar 2021 14:05:57 +0100 Subject: Cleanup: Move button context based operator poll into utility function Using the button context for operators is useful for other cases as well (where the operator isn't the button operator itself). For example we'll need this for the asset view UI template, where there will be additional operators that should be able to act on button context. --- source/blender/editors/interface/interface.c | 33 ++++++++++++++++------ .../blender/editors/interface/interface_intern.h | 2 ++ 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/source/blender/editors/interface/interface.c b/source/blender/editors/interface/interface.c index fbd7dcd61f2..279239fcc65 100644 --- a/source/blender/editors/interface/interface.c +++ b/source/blender/editors/interface/interface.c @@ -1816,6 +1816,29 @@ static void ui_but_validate(const uiBut *but) } #endif +/** + * Check if the operator \a ot poll is successfull with the context given by \a but (optionally). + * \param but: The button that might store context. Can be NULL for convenience (e.g. if there is + * no button to take context from, but we still want to poll the operator). + */ +bool ui_but_context_poll_operator(bContext *C, wmOperatorType *ot, const uiBut *but) +{ + bool result; + int opcontext = but ? but->opcontext : WM_OP_INVOKE_DEFAULT; + + if (but && but->context) { + CTX_store_set(C, but->context); + } + + result = WM_operator_poll_context(C, ot, opcontext); + + if (but && but->context) { + CTX_store_set(C, NULL); + } + + return result; +} + void UI_block_end_ex(const bContext *C, uiBlock *block, const int xy[2], int r_xy[2]) { wmWindow *window = CTX_wm_window(C); @@ -1841,17 +1864,9 @@ void UI_block_end_ex(const bContext *C, uiBlock *block, const int xy[2], int r_x if (but->optype) { wmOperatorType *ot = but->optype; - if (but->context) { - CTX_store_set((bContext *)C, but->context); - } - - if (ot == NULL || WM_operator_poll_context((bContext *)C, ot, but->opcontext) == 0) { + if (ot == NULL || !ui_but_context_poll_operator((bContext *)C, ot, but)) { but->flag |= UI_BUT_DISABLED; } - - if (but->context) { - CTX_store_set((bContext *)C, NULL); - } } const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct( diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h index 1d4a44e0c76..d9cfe97a3eb 100644 --- a/source/blender/editors/interface/interface_intern.h +++ b/source/blender/editors/interface/interface_intern.h @@ -647,6 +647,8 @@ extern bool ui_but_menu_draw_as_popover(const uiBut *but); void ui_but_range_set_hard(uiBut *but); void ui_but_range_set_soft(uiBut *but); +bool ui_but_context_poll_operator(struct bContext *C, struct wmOperatorType *ot, const uiBut *but); + extern void ui_but_update(uiBut *but); extern void ui_but_update_edited(uiBut *but); extern bool ui_but_is_float(const uiBut *but) ATTR_WARN_UNUSED_RESULT; -- cgit v1.2.3 From 392efc7d766b99055559086d5050fcb33017c849 Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Wed, 24 Mar 2021 14:25:30 +0100 Subject: Cleanup: Use const in region queries --- source/blender/editors/interface/interface_eyedropper.c | 2 +- source/blender/editors/interface/interface_handlers.c | 2 +- source/blender/editors/interface/interface_intern.h | 4 ++-- source/blender/editors/interface/interface_query.c | 7 +++++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/source/blender/editors/interface/interface_eyedropper.c b/source/blender/editors/interface/interface_eyedropper.c index fb8d32b3b84..178f663ff58 100644 --- a/source/blender/editors/interface/interface_eyedropper.c +++ b/source/blender/editors/interface/interface_eyedropper.c @@ -153,7 +153,7 @@ uiBut *eyedropper_get_property_button_under_mouse(bContext *C, const wmEvent *ev { bScreen *screen = CTX_wm_screen(C); ScrArea *area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, event->x, event->y); - ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_ANY, event->x, event->y); + const ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_ANY, event->x, event->y); uiBut *but = ui_but_find_mouse_over(region, event); diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c index ee5c3f53f5e..a5a5a69728e 100644 --- a/source/blender/editors/interface/interface_handlers.c +++ b/source/blender/editors/interface/interface_handlers.c @@ -1594,7 +1594,7 @@ static int ui_handler_region_drag_toggle(bContext *C, const wmEvent *event, void if (done) { wmWindow *win = CTX_wm_window(C); - ARegion *region = CTX_wm_region(C); + const ARegion *region = CTX_wm_region(C); uiBut *but = ui_but_find_mouse_over_ex( region, drag_info->xy_init[0], drag_info->xy_init[1], true); diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h index d9cfe97a3eb..4c96512b4f3 100644 --- a/source/blender/editors/interface/interface_intern.h +++ b/source/blender/editors/interface/interface_intern.h @@ -1110,11 +1110,11 @@ bool ui_but_contains_point_px(const uiBut *but, const struct ARegion *region, in uiBut *ui_list_find_mouse_over(struct ARegion *region, const struct wmEvent *event) ATTR_WARN_UNUSED_RESULT; -uiBut *ui_but_find_mouse_over_ex(struct ARegion *region, +uiBut *ui_but_find_mouse_over_ex(const struct ARegion *region, const int x, const int y, const bool labeledit) ATTR_WARN_UNUSED_RESULT; -uiBut *ui_but_find_mouse_over(struct ARegion *region, +uiBut *ui_but_find_mouse_over(const struct ARegion *region, const struct wmEvent *event) ATTR_WARN_UNUSED_RESULT; uiBut *ui_but_find_rect_over(const struct ARegion *region, const rcti *rect_px) ATTR_WARN_UNUSED_RESULT; diff --git a/source/blender/editors/interface/interface_query.c b/source/blender/editors/interface/interface_query.c index 83e48fad157..aa10d092f5e 100644 --- a/source/blender/editors/interface/interface_query.c +++ b/source/blender/editors/interface/interface_query.c @@ -265,7 +265,10 @@ bool ui_but_contains_point_px_icon(const uiBut *but, ARegion *region, const wmEv } /* x and y are only used in case event is NULL... */ -uiBut *ui_but_find_mouse_over_ex(ARegion *region, const int x, const int y, const bool labeledit) +uiBut *ui_but_find_mouse_over_ex(const ARegion *region, + const int x, + const int y, + const bool labeledit) { uiBut *butover = NULL; @@ -303,7 +306,7 @@ uiBut *ui_but_find_mouse_over_ex(ARegion *region, const int x, const int y, cons return butover; } -uiBut *ui_but_find_mouse_over(ARegion *region, const wmEvent *event) +uiBut *ui_but_find_mouse_over(const ARegion *region, const wmEvent *event) { return ui_but_find_mouse_over_ex(region, event->x, event->y, event->ctrl != 0); } -- cgit v1.2.3 From 0d5cef989803d047696b5f76f0cc95a8cc07a25a Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Wed, 24 Mar 2021 12:53:41 +0100 Subject: Cleanup: removed unnecessary allocations. --- source/blender/compositor/intern/COM_CPUDevice.h | 2 +- source/blender/compositor/intern/COM_Device.h | 15 ----- .../blender/compositor/intern/COM_OpenCLDevice.cc | 6 +- .../blender/compositor/intern/COM_OpenCLDevice.h | 17 +----- .../blender/compositor/intern/COM_WorkScheduler.cc | 70 +++++++--------------- 5 files changed, 27 insertions(+), 83 deletions(-) diff --git a/source/blender/compositor/intern/COM_CPUDevice.h b/source/blender/compositor/intern/COM_CPUDevice.h index 962380d7bc8..6df1f41419d 100644 --- a/source/blender/compositor/intern/COM_CPUDevice.h +++ b/source/blender/compositor/intern/COM_CPUDevice.h @@ -33,7 +33,7 @@ class CPUDevice : public Device { * \brief execute a WorkPackage * \param work: the WorkPackage to execute */ - void execute(WorkPackage *work); + void execute(WorkPackage *work) override; int thread_id() { diff --git a/source/blender/compositor/intern/COM_Device.h b/source/blender/compositor/intern/COM_Device.h index 0b0f0f5c1c6..0a456760045 100644 --- a/source/blender/compositor/intern/COM_Device.h +++ b/source/blender/compositor/intern/COM_Device.h @@ -36,21 +36,6 @@ class Device { { } - /** - * \brief initialize the device - */ - virtual bool initialize() - { - return true; - } - - /** - * \brief deinitialize the device - */ - virtual void deinitialize() - { - } - /** * \brief execute a WorkPackage * \param work: the WorkPackage to execute diff --git a/source/blender/compositor/intern/COM_OpenCLDevice.cc b/source/blender/compositor/intern/COM_OpenCLDevice.cc index 9a6012e5c68..4ac6bd50380 100644 --- a/source/blender/compositor/intern/COM_OpenCLDevice.cc +++ b/source/blender/compositor/intern/COM_OpenCLDevice.cc @@ -43,16 +43,12 @@ OpenCLDevice::OpenCLDevice(cl_context context, this->m_program = program; this->m_queue = nullptr; this->m_vendorID = vendorId; -} -bool OpenCLDevice::initialize() -{ cl_int error; this->m_queue = clCreateCommandQueue(this->m_context, this->m_device, 0, &error); - return false; } -void OpenCLDevice::deinitialize() +OpenCLDevice::~OpenCLDevice() { if (this->m_queue) { clReleaseCommandQueue(this->m_queue); diff --git a/source/blender/compositor/intern/COM_OpenCLDevice.h b/source/blender/compositor/intern/COM_OpenCLDevice.h index e4fd397b4e8..30d9a59d182 100644 --- a/source/blender/compositor/intern/COM_OpenCLDevice.h +++ b/source/blender/compositor/intern/COM_OpenCLDevice.h @@ -65,26 +65,13 @@ class OpenCLDevice : public Device { * \param vendorID: */ OpenCLDevice(cl_context context, cl_device_id device, cl_program program, cl_int vendorId); - - /** - * \brief initialize the device - * During initialization the OpenCL cl_command_queue is created - * the command queue is stored in the field queue. - * \see queue - */ - bool initialize(); - - /** - * \brief de-initialize the device - * During de-initialization the command queue is cleared - */ - void deinitialize(); + ~OpenCLDevice(); /** * \brief execute a WorkPackage * \param work: the WorkPackage to execute */ - void execute(WorkPackage *work); + void execute(WorkPackage *work) override; /** * \brief determine an image format diff --git a/source/blender/compositor/intern/COM_WorkScheduler.cc b/source/blender/compositor/intern/COM_WorkScheduler.cc index 56956b9d097..7d9dd502762 100644 --- a/source/blender/compositor/intern/COM_WorkScheduler.cc +++ b/source/blender/compositor/intern/COM_WorkScheduler.cc @@ -70,7 +70,7 @@ static struct { /** \brief list of all CPUDevices. for every hardware thread an instance of CPUDevice is * created */ - blender::Vector devices; + blender::Vector devices; /** \brief list of all thread for every CPUDevice in cpudevices a thread exists. */ ListBase threads; @@ -89,7 +89,7 @@ static struct { cl_program program; /** \brief list of all OpenCLDevices. for every OpenCL GPU device an instance of OpenCLDevice * is created. */ - blender::Vector devices; + blender::Vector devices; /** \brief list of all thread for every GPUDevice in cpudevices a thread exists. */ ListBase threads; /** \brief all scheduled work for the GPU. */ @@ -130,9 +130,8 @@ static void opencl_start(CompositorContext &context) BLI_threadpool_init(&g_work_scheduler.opencl.threads, thread_execute_gpu, g_work_scheduler.opencl.devices.size()); - for (int index = 0; index < g_work_scheduler.opencl.devices.size(); index++) { - Device *device = g_work_scheduler.opencl.devices[index]; - BLI_threadpool_insert(&g_work_scheduler.opencl.threads, device); + for (Device &device : g_work_scheduler.opencl.devices) { + BLI_threadpool_insert(&g_work_scheduler.opencl.threads, &device); } g_work_scheduler.opencl.active = true; } @@ -263,12 +262,10 @@ static void opencl_initialize(const bool use_opencl) if (error2 != CL_SUCCESS) { printf("CLERROR[%d]: %s\n", error2, clewErrorString(error2)); } - OpenCLDevice *clDevice = new OpenCLDevice(g_work_scheduler.opencl.context, - device, - g_work_scheduler.opencl.program, - vendorID); - clDevice->initialize(); - g_work_scheduler.opencl.devices.append(clDevice); + g_work_scheduler.opencl.devices.append(OpenCLDevice(g_work_scheduler.opencl.context, + device, + g_work_scheduler.opencl.program, + vendorID)); } } MEM_freeN(cldevices); @@ -282,26 +279,19 @@ static void opencl_initialize(const bool use_opencl) static void opencl_deinitialize() { - /* Deinitialize OpenCL GPU's. */ - if (g_work_scheduler.opencl.initialized) { - while (!g_work_scheduler.opencl.devices.is_empty()) { - Device *device = g_work_scheduler.opencl.devices.pop_last(); - device->deinitialize(); - delete device; - } - g_work_scheduler.opencl.devices.clear_and_make_inline(); + g_work_scheduler.opencl.devices.clear_and_make_inline(); - if (g_work_scheduler.opencl.program) { - clReleaseProgram(g_work_scheduler.opencl.program); - g_work_scheduler.opencl.program = nullptr; - } - if (g_work_scheduler.opencl.context) { - clReleaseContext(g_work_scheduler.opencl.context); - g_work_scheduler.opencl.context = nullptr; - } + if (g_work_scheduler.opencl.program) { + clReleaseProgram(g_work_scheduler.opencl.program); + g_work_scheduler.opencl.program = nullptr; + } - g_work_scheduler.opencl.initialized = false; + if (g_work_scheduler.opencl.context) { + clReleaseContext(g_work_scheduler.opencl.context); + g_work_scheduler.opencl.context = nullptr; } + + g_work_scheduler.opencl.initialized = false; } /* \} */ @@ -347,9 +337,8 @@ static void threading_model_queue_start() BLI_threadpool_init(&g_work_scheduler.queue.threads, threading_model_queue_execute, g_work_scheduler.queue.devices.size()); - for (int index = 0; index < g_work_scheduler.queue.devices.size(); index++) { - Device *device = g_work_scheduler.queue.devices[index]; - BLI_threadpool_insert(&g_work_scheduler.queue.threads, device); + for (Device &device : g_work_scheduler.queue.devices) { + BLI_threadpool_insert(&g_work_scheduler.queue.threads, &device); } } @@ -370,25 +359,17 @@ static void threading_model_queue_initialize(const int num_cpu_threads) { /* Reinitialize if number of threads doesn't match. */ if (g_work_scheduler.queue.devices.size() != num_cpu_threads) { - Device *device; - - while (!g_work_scheduler.queue.devices.is_empty()) { - device = g_work_scheduler.queue.devices.pop_last(); - device->deinitialize(); - delete device; - } + g_work_scheduler.queue.devices.clear(); if (g_work_scheduler.queue.initialized) { BLI_thread_local_delete(g_thread_device); + g_work_scheduler.queue.initialized = false; } - g_work_scheduler.queue.initialized = false; } /* Initialize CPU threads. */ if (!g_work_scheduler.queue.initialized) { for (int index = 0; index < num_cpu_threads; index++) { - CPUDevice *device = new CPUDevice(index); - device->initialize(); - g_work_scheduler.queue.devices.append(device); + g_work_scheduler.queue.devices.append(CPUDevice(index)); } BLI_thread_local_create(g_thread_device); g_work_scheduler.queue.initialized = true; @@ -398,11 +379,6 @@ static void threading_model_queue_deinitialize() { /* deinitialize CPU threads */ if (g_work_scheduler.queue.initialized) { - while (!g_work_scheduler.queue.devices.is_empty()) { - Device *device = g_work_scheduler.queue.devices.pop_last(); - device->deinitialize(); - delete device; - } g_work_scheduler.queue.devices.clear_and_make_inline(); BLI_thread_local_delete(g_thread_device); -- cgit v1.2.3 From 31f8d894b99a59ecb3b59f1bf6c558a3944fa988 Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Wed, 24 Mar 2021 13:40:52 +0100 Subject: Cleanup: replace index based forloops with iterators. --- .../compositor/intern/COM_ExecutionGroup.cc | 11 +-- .../compositor/intern/COM_ExecutionSystem.cc | 110 +++++++++++---------- .../compositor/intern/COM_ExecutionSystem.h | 6 -- 3 files changed, 60 insertions(+), 67 deletions(-) diff --git a/source/blender/compositor/intern/COM_ExecutionGroup.cc b/source/blender/compositor/intern/COM_ExecutionGroup.cc index 3cedc5da663..75e5c4d3178 100644 --- a/source/blender/compositor/intern/COM_ExecutionGroup.cc +++ b/source/blender/compositor/intern/COM_ExecutionGroup.cc @@ -130,9 +130,7 @@ void ExecutionGroup::initExecution() if (this->m_chunks_len != 0) { m_chunk_execution_states.resize(this->m_chunks_len); - for (int index = 0; index < this->m_chunks_len; index++) { - m_chunk_execution_states[index] = eChunkExecutionState::NOT_SCHEDULED; - } + m_chunk_execution_states.fill(eChunkExecutionState::NOT_SCHEDULED); } unsigned int max_offset = 0; @@ -185,7 +183,6 @@ void ExecutionGroup::determineNumberOfChunks() blender::Array ExecutionGroup::determine_chunk_execution_order() const { - int index; blender::Array chunk_order(m_chunks_len); for (int chunk_index = 0; chunk_index < this->m_chunks_len; chunk_index++) { chunk_order[chunk_index] = chunk_index; @@ -205,7 +202,7 @@ blender::Array ExecutionGroup::determine_chunk_execution_order() c const int border_width = BLI_rcti_size_x(&this->m_viewerBorder); const int border_height = BLI_rcti_size_y(&this->m_viewerBorder); - + int index; switch (order_type) { case ChunkOrdering::Random: { static blender::RandomNumberGenerator rng; @@ -303,7 +300,6 @@ void ExecutionGroup::execute(ExecutionSystem *graph) this->m_chunks_finished = 0; this->m_bTree = bTree; - unsigned int index; blender::Array chunk_order = determine_chunk_execution_order(); @@ -320,7 +316,8 @@ void ExecutionGroup::execute(ExecutionSystem *graph) finished = true; int numberEvaluated = 0; - for (index = startIndex; index < this->m_chunks_len && numberEvaluated < maxNumberEvaluated; + for (int index = startIndex; + index < this->m_chunks_len && numberEvaluated < maxNumberEvaluated; index++) { chunk_index = chunk_order[index]; int yChunk = chunk_index / this->m_x_chunks_len; diff --git a/source/blender/compositor/intern/COM_ExecutionSystem.cc b/source/blender/compositor/intern/COM_ExecutionSystem.cc index 993aedf7715..b28ee79c359 100644 --- a/source/blender/compositor/intern/COM_ExecutionSystem.cc +++ b/source/blender/compositor/intern/COM_ExecutionSystem.cc @@ -71,7 +71,6 @@ ExecutionSystem::ExecutionSystem(RenderData *rd, builder.convertToOperations(this); } - unsigned int index; unsigned int resolution[2]; rctf *viewer_border = &editingtree->viewer_border; @@ -81,10 +80,9 @@ ExecutionSystem::ExecutionSystem(RenderData *rd, editingtree->stats_draw(editingtree->sdh, TIP_("Compositing | Determining resolution")); - for (index = 0; index < this->m_groups.size(); index++) { + for (ExecutionGroup *executionGroup : m_groups) { resolution[0] = 0; resolution[1] = 0; - ExecutionGroup *executionGroup = this->m_groups[index]; executionGroup->determineResolution(resolution); if (rendering) { @@ -108,14 +106,12 @@ ExecutionSystem::ExecutionSystem(RenderData *rd, ExecutionSystem::~ExecutionSystem() { - unsigned int index; - for (index = 0; index < this->m_operations.size(); index++) { - NodeOperation *operation = this->m_operations[index]; + for (NodeOperation *operation : m_operations) { delete operation; } this->m_operations.clear(); - for (index = 0; index < this->m_groups.size(); index++) { - ExecutionGroup *group = this->m_groups[index]; + + for (ExecutionGroup *group : m_groups) { delete group; } this->m_groups.clear(); @@ -128,92 +124,98 @@ void ExecutionSystem::set_operations(const blender::Vector &ope m_groups = groups; } -void ExecutionSystem::execute() +static void update_read_buffer_offset(blender::Vector &operations) { - const bNodeTree *editingtree = this->m_context.getbNodeTree(); - editingtree->stats_draw(editingtree->sdh, TIP_("Compositing | Initializing execution")); - - DebugInfo::execute_started(this); - unsigned int order = 0; - for (NodeOperation *operation : m_operations) { + for (NodeOperation *operation : operations) { if (operation->isReadBufferOperation()) { ReadBufferOperation *readOperation = (ReadBufferOperation *)operation; readOperation->setOffset(order); order++; } } - unsigned int index; +} - // First allocale all write buffer - for (index = 0; index < this->m_operations.size(); index++) { - NodeOperation *operation = this->m_operations[index]; +static void init_write_operations_for_execution(blender::Vector &operations, + const bNodeTree *bTree) +{ + for (NodeOperation *operation : operations) { if (operation->isWriteBufferOperation()) { - operation->setbNodeTree(this->m_context.getbNodeTree()); + operation->setbNodeTree(bTree); operation->initExecution(); } } - // Connect read buffers to their write buffers - for (index = 0; index < this->m_operations.size(); index++) { - NodeOperation *operation = this->m_operations[index]; +} + +static void link_write_buffers(blender::Vector &operations) +{ + for (NodeOperation *operation : operations) { if (operation->isReadBufferOperation()) { - ReadBufferOperation *readOperation = (ReadBufferOperation *)operation; + ReadBufferOperation *readOperation = static_cast(operation); readOperation->updateMemoryBuffer(); } } - // initialize other operations - for (index = 0; index < this->m_operations.size(); index++) { - NodeOperation *operation = this->m_operations[index]; +} + +static void init_non_write_operations_for_execution(blender::Vector &operations, + const bNodeTree *bTree) +{ + for (NodeOperation *operation : operations) { if (!operation->isWriteBufferOperation()) { - operation->setbNodeTree(this->m_context.getbNodeTree()); + operation->setbNodeTree(bTree); operation->initExecution(); } } - for (index = 0; index < this->m_groups.size(); index++) { - ExecutionGroup *executionGroup = this->m_groups[index]; - executionGroup->setChunksize(this->m_context.getChunksize()); - executionGroup->initExecution(); +} + +static void init_execution_groups_for_execution(blender::Vector &groups, + const int chunk_size) +{ + for (ExecutionGroup *execution_group : groups) { + execution_group->setChunksize(chunk_size); + execution_group->initExecution(); } +} - WorkScheduler::start(this->m_context); +void ExecutionSystem::execute() +{ + const bNodeTree *editingtree = this->m_context.getbNodeTree(); + editingtree->stats_draw(editingtree->sdh, TIP_("Compositing | Initializing execution")); + + DebugInfo::execute_started(this); + update_read_buffer_offset(m_operations); + init_write_operations_for_execution(m_operations, m_context.getbNodeTree()); + link_write_buffers(m_operations); + init_non_write_operations_for_execution(m_operations, m_context.getbNodeTree()); + init_execution_groups_for_execution(m_groups, m_context.getChunksize()); + + WorkScheduler::start(this->m_context); execute_groups(CompositorPriority::High); if (!this->getContext().isFastCalculation()) { execute_groups(CompositorPriority::Medium); execute_groups(CompositorPriority::Low); } - WorkScheduler::finish(); WorkScheduler::stop(); editingtree->stats_draw(editingtree->sdh, TIP_("Compositing | De-initializing execution")); - for (index = 0; index < this->m_operations.size(); index++) { - NodeOperation *operation = this->m_operations[index]; + + for (NodeOperation *operation : m_operations) { operation->deinitExecution(); } - for (index = 0; index < this->m_groups.size(); index++) { - ExecutionGroup *executionGroup = this->m_groups[index]; - executionGroup->deinitExecution(); - } -} -void ExecutionSystem::execute_groups(CompositorPriority priority) -{ - blender::Vector execution_groups = find_output_execution_groups(priority); - for (ExecutionGroup *group : execution_groups) { - group->execute(this); + for (ExecutionGroup *execution_group : m_groups) { + execution_group->deinitExecution(); } } -blender::Vector ExecutionSystem::find_output_execution_groups( - CompositorPriority priority) const +void ExecutionSystem::execute_groups(CompositorPriority priority) { - blender::Vector result; - - for (ExecutionGroup *group : m_groups) { - if (group->isOutputExecutionGroup() && group->getRenderPriotrity() == priority) { - result.append(group); + for (ExecutionGroup *execution_group : m_groups) { + if (execution_group->isOutputExecutionGroup() && + execution_group->getRenderPriotrity() == priority) { + execution_group->execute(this); } } - return result; } diff --git a/source/blender/compositor/intern/COM_ExecutionSystem.h b/source/blender/compositor/intern/COM_ExecutionSystem.h index 6a50cc6906b..c12380fe839 100644 --- a/source/blender/compositor/intern/COM_ExecutionSystem.h +++ b/source/blender/compositor/intern/COM_ExecutionSystem.h @@ -135,12 +135,6 @@ class ExecutionSystem { blender::Vector m_groups; private: // methods - /** - * find all execution group with output nodes - */ - blender::Vector find_output_execution_groups( - CompositorPriority priority) const; - public: /** * \brief Create a new ExecutionSystem and initialize it with the -- cgit v1.2.3 From b2246d25a29aaf81ff63b98cae3c096194a6a0fa Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Wed, 24 Mar 2021 13:42:55 +0100 Subject: Cleanup: Removed commented out code. --- source/blender/compositor/intern/COM_MemoryProxy.h | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/source/blender/compositor/intern/COM_MemoryProxy.h b/source/blender/compositor/intern/COM_MemoryProxy.h index a40e6f95dce..ee98ff41630 100644 --- a/source/blender/compositor/intern/COM_MemoryProxy.h +++ b/source/blender/compositor/intern/COM_MemoryProxy.h @@ -44,16 +44,6 @@ class MemoryProxy { */ ExecutionGroup *m_executor; - /** - * \brief datatype of this MemoryProxy - */ - /* DataType m_datatype; */ /* UNUSED */ - - /** - * \brief channel information of this buffer - */ - /* ChannelInfo m_channelInfo[COM_NUMBER_OF_CHANNELS]; */ /* UNUSED */ - /** * \brief the allocated memory */ -- cgit v1.2.3 From c06042afc6c6cbfef52078b44f2b10cf5488bb27 Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Wed, 24 Mar 2021 14:27:41 +0100 Subject: Spelling: getRenderPriotrity -> getRenderPriority --- source/blender/compositor/intern/COM_ExecutionGroup.cc | 2 +- source/blender/compositor/intern/COM_ExecutionGroup.h | 2 +- source/blender/compositor/intern/COM_ExecutionSystem.cc | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/source/blender/compositor/intern/COM_ExecutionGroup.cc b/source/blender/compositor/intern/COM_ExecutionGroup.cc index 75e5c4d3178..f500327b7a7 100644 --- a/source/blender/compositor/intern/COM_ExecutionGroup.cc +++ b/source/blender/compositor/intern/COM_ExecutionGroup.cc @@ -65,7 +65,7 @@ ExecutionGroup::ExecutionGroup() this->m_executionStartTime = 0; } -CompositorPriority ExecutionGroup::getRenderPriotrity() +CompositorPriority ExecutionGroup::getRenderPriority() { return this->getOutputOperation()->getRenderPriority(); } diff --git a/source/blender/compositor/intern/COM_ExecutionGroup.h b/source/blender/compositor/intern/COM_ExecutionGroup.h index 6fd93d2d93c..13ff06cd5d1 100644 --- a/source/blender/compositor/intern/COM_ExecutionGroup.h +++ b/source/blender/compositor/intern/COM_ExecutionGroup.h @@ -424,7 +424,7 @@ class ExecutionGroup { * \brief get the Render priority of this ExecutionGroup * \see ExecutionSystem.execute */ - CompositorPriority getRenderPriotrity(); + CompositorPriority getRenderPriority(); /** * \brief set border for viewer operation diff --git a/source/blender/compositor/intern/COM_ExecutionSystem.cc b/source/blender/compositor/intern/COM_ExecutionSystem.cc index b28ee79c359..df97b8079b2 100644 --- a/source/blender/compositor/intern/COM_ExecutionSystem.cc +++ b/source/blender/compositor/intern/COM_ExecutionSystem.cc @@ -214,7 +214,7 @@ void ExecutionSystem::execute_groups(CompositorPriority priority) { for (ExecutionGroup *execution_group : m_groups) { if (execution_group->isOutputExecutionGroup() && - execution_group->getRenderPriotrity() == priority) { + execution_group->getRenderPriority() == priority) { execution_group->execute(this); } } -- cgit v1.2.3 From 93bedcb96ac8036bfa9649f5bc7aaee28762bb9c Mon Sep 17 00:00:00 2001 From: Dalai Felinto Date: Wed, 24 Mar 2021 14:46:25 +0100 Subject: Fix T86895: bpy.ops.curve.select_all(action='INVERT') The operator should not assume v3d is valid. --- source/blender/editors/curve/editcurve_select.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/blender/editors/curve/editcurve_select.c b/source/blender/editors/curve/editcurve_select.c index d362ec23370..e3fc8b73172 100644 --- a/source/blender/editors/curve/editcurve_select.c +++ b/source/blender/editors/curve/editcurve_select.c @@ -578,8 +578,8 @@ static int de_select_all_exec(bContext *C, wmOperator *op) changed = ED_curve_deselect_all(cu->editnurb); break; case SEL_INVERT: - changed = ED_curve_select_swap(cu->editnurb, - v3d->overlay.handle_display == CURVE_HANDLE_NONE); + changed = ED_curve_select_swap( + cu->editnurb, (v3d && (v3d->overlay.handle_display == CURVE_HANDLE_NONE))); break; } -- cgit v1.2.3 From 4d18fa268cf3f8db7e448c7408738b2bd497250b Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Wed, 24 Mar 2021 10:46:38 +0100 Subject: Tweak default ID types processing order, step 01: Collections. Move `OB_GR` processing just after scenes, and before objects. This is much more sensible in general, and fixes glitches in auto-resync process of library overrides in particular. --- source/blender/makesdna/DNA_ID.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/makesdna/DNA_ID.h b/source/blender/makesdna/DNA_ID.h index 077f9bf8bdc..76676c0ebc1 100644 --- a/source/blender/makesdna/DNA_ID.h +++ b/source/blender/makesdna/DNA_ID.h @@ -787,7 +787,6 @@ enum { INDEX_ID_CA, INDEX_ID_TXT, INDEX_ID_SO, - INDEX_ID_GR, INDEX_ID_PC, INDEX_ID_BR, INDEX_ID_PA, @@ -798,6 +797,7 @@ enum { INDEX_ID_SCR, INDEX_ID_OB, INDEX_ID_LS, + INDEX_ID_GR, INDEX_ID_SCE, INDEX_ID_WS, INDEX_ID_WM, -- cgit v1.2.3 From 39bf5e5022a643a7e46182d3157cc940dbd21e55 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Wed, 24 Mar 2021 10:54:05 +0100 Subject: Tweak default ID types processing order, step 02: Simulations. This is a fairly low-level ID type, so it needs to be higher in the list, for now put it just before the shading-related types. --- source/blender/makesdna/DNA_ID.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/source/blender/makesdna/DNA_ID.h b/source/blender/makesdna/DNA_ID.h index 76676c0ebc1..258913f8e3e 100644 --- a/source/blender/makesdna/DNA_ID.h +++ b/source/blender/makesdna/DNA_ID.h @@ -773,6 +773,7 @@ enum { INDEX_ID_IM, INDEX_ID_TE, INDEX_ID_MA, + INDEX_ID_SIM, INDEX_ID_VF, INDEX_ID_AR, INDEX_ID_CF, @@ -801,10 +802,7 @@ enum { INDEX_ID_SCE, INDEX_ID_WS, INDEX_ID_WM, - /* TODO: This should probably be tweaked, #Mask and #Simulation are rather low-level types that - * should most likely be defined //before// #Object and geometry type indices? */ INDEX_ID_MSK, - INDEX_ID_SIM, INDEX_ID_NULL, INDEX_ID_MAX, }; -- cgit v1.2.3 From 950f0b8b97b0a40e87258743b578d0cc02c9f67f Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Wed, 24 Mar 2021 10:56:00 +0100 Subject: Tweak default ID types processing order, step 03: Mask. Move it with the rest of the image/shading related types. --- source/blender/makesdna/DNA_ID.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/makesdna/DNA_ID.h b/source/blender/makesdna/DNA_ID.h index 258913f8e3e..9c5dafb2902 100644 --- a/source/blender/makesdna/DNA_ID.h +++ b/source/blender/makesdna/DNA_ID.h @@ -770,6 +770,7 @@ enum { INDEX_ID_PAL, INDEX_ID_GD, INDEX_ID_NT, + INDEX_ID_MSK, INDEX_ID_IM, INDEX_ID_TE, INDEX_ID_MA, @@ -802,7 +803,6 @@ enum { INDEX_ID_SCE, INDEX_ID_WS, INDEX_ID_WM, - INDEX_ID_MSK, INDEX_ID_NULL, INDEX_ID_MAX, }; -- cgit v1.2.3 From e96c286e53ad63c6e81ed7d681ee438a74a49444 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Wed, 24 Mar 2021 10:57:04 +0100 Subject: Tweak default ID types processing order, step 04: Screen. Move Screen data type higher in the list, just after the other UI-related types, and before Scenes. --- source/blender/makesdna/DNA_ID.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/makesdna/DNA_ID.h b/source/blender/makesdna/DNA_ID.h index 9c5dafb2902..c5fcb85f644 100644 --- a/source/blender/makesdna/DNA_ID.h +++ b/source/blender/makesdna/DNA_ID.h @@ -796,11 +796,11 @@ enum { INDEX_ID_LP, INDEX_ID_WO, INDEX_ID_MC, - INDEX_ID_SCR, INDEX_ID_OB, INDEX_ID_LS, INDEX_ID_GR, INDEX_ID_SCE, + INDEX_ID_SCR, INDEX_ID_WS, INDEX_ID_WM, INDEX_ID_NULL, -- cgit v1.2.3 From c0f5f9ad6e3b3dcace39ba0891064a76f0790985 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Wed, 24 Mar 2021 11:01:52 +0100 Subject: Tweak default ID types processing order, step 05: PaintCurve. Move PaintCurve type with other weird, not-really-data ID types (like Palette and Brushes), higher in the process. Those preset-like types may use a lot of other ID types, but should only be used by UI-related (and Scene, for tool settings) types. --- source/blender/makesdna/DNA_ID.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/makesdna/DNA_ID.h b/source/blender/makesdna/DNA_ID.h index c5fcb85f644..c233985fc0e 100644 --- a/source/blender/makesdna/DNA_ID.h +++ b/source/blender/makesdna/DNA_ID.h @@ -789,7 +789,6 @@ enum { INDEX_ID_CA, INDEX_ID_TXT, INDEX_ID_SO, - INDEX_ID_PC, INDEX_ID_BR, INDEX_ID_PA, INDEX_ID_SPK, @@ -799,6 +798,7 @@ enum { INDEX_ID_OB, INDEX_ID_LS, INDEX_ID_GR, + INDEX_ID_PC, INDEX_ID_SCE, INDEX_ID_SCR, INDEX_ID_WS, -- cgit v1.2.3 From 72fa0d48ea3dd531ec17aacb2052d59f3feaa4d5 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Wed, 24 Mar 2021 12:50:03 +0100 Subject: Tweak default ID types processing order, step 06: Brushes. Move Brushes type with other weird, not-really-data ID types (like Palette and Brushes), higher in the process. Those preset-like types may use a lot of other ID types, but should only be used by UI-related (and Scene, for tool settings) types. --- source/blender/makesdna/DNA_ID.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/makesdna/DNA_ID.h b/source/blender/makesdna/DNA_ID.h index c233985fc0e..05a8e9a0b30 100644 --- a/source/blender/makesdna/DNA_ID.h +++ b/source/blender/makesdna/DNA_ID.h @@ -789,7 +789,6 @@ enum { INDEX_ID_CA, INDEX_ID_TXT, INDEX_ID_SO, - INDEX_ID_BR, INDEX_ID_PA, INDEX_ID_SPK, INDEX_ID_LP, @@ -799,6 +798,7 @@ enum { INDEX_ID_LS, INDEX_ID_GR, INDEX_ID_PC, + INDEX_ID_BR, INDEX_ID_SCE, INDEX_ID_SCR, INDEX_ID_WS, -- cgit v1.2.3 From a35d145b72c7f9afc916507fc32bf631710e7286 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Wed, 24 Mar 2021 11:22:56 +0100 Subject: Tweak default ID types processing order, step 07: MovieClips. Move them with the other shading/image types, just before Images. --- source/blender/makesdna/DNA_ID.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/makesdna/DNA_ID.h b/source/blender/makesdna/DNA_ID.h index 05a8e9a0b30..9326f034dc6 100644 --- a/source/blender/makesdna/DNA_ID.h +++ b/source/blender/makesdna/DNA_ID.h @@ -772,6 +772,7 @@ enum { INDEX_ID_NT, INDEX_ID_MSK, INDEX_ID_IM, + INDEX_ID_MC, INDEX_ID_TE, INDEX_ID_MA, INDEX_ID_SIM, @@ -793,7 +794,6 @@ enum { INDEX_ID_SPK, INDEX_ID_LP, INDEX_ID_WO, - INDEX_ID_MC, INDEX_ID_OB, INDEX_ID_LS, INDEX_ID_GR, -- cgit v1.2.3 From be073d8baf409b2f20e5fe10eeb908b09a701cb7 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Wed, 24 Mar 2021 11:24:16 +0100 Subject: Tweak default ID types processing order, step 08: CacheFiles. Move them with the rest of the simulation types. --- source/blender/makesdna/DNA_ID.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/makesdna/DNA_ID.h b/source/blender/makesdna/DNA_ID.h index 9326f034dc6..db77592c441 100644 --- a/source/blender/makesdna/DNA_ID.h +++ b/source/blender/makesdna/DNA_ID.h @@ -775,10 +775,10 @@ enum { INDEX_ID_MC, INDEX_ID_TE, INDEX_ID_MA, + INDEX_ID_CF, INDEX_ID_SIM, INDEX_ID_VF, INDEX_ID_AR, - INDEX_ID_CF, INDEX_ID_ME, INDEX_ID_CU, INDEX_ID_MB, -- cgit v1.2.3 From 0e6088f4672b51abdef997779197bc727a6886d1 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Wed, 24 Mar 2021 11:28:03 +0100 Subject: Tweak default ID types processing order, step 09: Particles. Move Particles next to the other physics/simulation types, after obdata types. --- source/blender/makesdna/DNA_ID.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/makesdna/DNA_ID.h b/source/blender/makesdna/DNA_ID.h index db77592c441..7d4b1ab2d17 100644 --- a/source/blender/makesdna/DNA_ID.h +++ b/source/blender/makesdna/DNA_ID.h @@ -777,6 +777,7 @@ enum { INDEX_ID_MA, INDEX_ID_CF, INDEX_ID_SIM, + INDEX_ID_PA, INDEX_ID_VF, INDEX_ID_AR, INDEX_ID_ME, @@ -790,7 +791,6 @@ enum { INDEX_ID_CA, INDEX_ID_TXT, INDEX_ID_SO, - INDEX_ID_PA, INDEX_ID_SPK, INDEX_ID_LP, INDEX_ID_WO, -- cgit v1.2.3 From ae84a1363d1a7ad6b2251b8ac2d1f240af8d894b Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Wed, 24 Mar 2021 11:29:31 +0100 Subject: Tweak default ID types processing order, step 10: LineStyles. Move LineStyles (Freestyle) type with the rest of the shading types. --- source/blender/makesdna/DNA_ID.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/makesdna/DNA_ID.h b/source/blender/makesdna/DNA_ID.h index 7d4b1ab2d17..d1751c0948f 100644 --- a/source/blender/makesdna/DNA_ID.h +++ b/source/blender/makesdna/DNA_ID.h @@ -775,6 +775,7 @@ enum { INDEX_ID_MC, INDEX_ID_TE, INDEX_ID_MA, + INDEX_ID_LS, INDEX_ID_CF, INDEX_ID_SIM, INDEX_ID_PA, @@ -795,7 +796,6 @@ enum { INDEX_ID_LP, INDEX_ID_WO, INDEX_ID_OB, - INDEX_ID_LS, INDEX_ID_GR, INDEX_ID_PC, INDEX_ID_BR, -- cgit v1.2.3 From f3fe3d965ffef91728b36821c41cc8773d44c49f Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Wed, 24 Mar 2021 11:31:00 +0100 Subject: Tweak default ID types processing order, step 11: Worlds. Move World type with the other shading types. --- source/blender/makesdna/DNA_ID.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/makesdna/DNA_ID.h b/source/blender/makesdna/DNA_ID.h index d1751c0948f..dddcafe5f8f 100644 --- a/source/blender/makesdna/DNA_ID.h +++ b/source/blender/makesdna/DNA_ID.h @@ -776,6 +776,7 @@ enum { INDEX_ID_TE, INDEX_ID_MA, INDEX_ID_LS, + INDEX_ID_WO, INDEX_ID_CF, INDEX_ID_SIM, INDEX_ID_PA, @@ -794,7 +795,6 @@ enum { INDEX_ID_SO, INDEX_ID_SPK, INDEX_ID_LP, - INDEX_ID_WO, INDEX_ID_OB, INDEX_ID_GR, INDEX_ID_PC, -- cgit v1.2.3 From fe55ede5ce25180cb06285bda55d3c119eda1a88 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Wed, 24 Mar 2021 11:35:26 +0100 Subject: Tweak default ID types processing order, step 12: Sounds. Move Sound type toward the end of the list, this is a file-wrapper type with typically no dependency to any other ID. --- source/blender/makesdna/DNA_ID.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/makesdna/DNA_ID.h b/source/blender/makesdna/DNA_ID.h index dddcafe5f8f..4c447045d8a 100644 --- a/source/blender/makesdna/DNA_ID.h +++ b/source/blender/makesdna/DNA_ID.h @@ -770,6 +770,7 @@ enum { INDEX_ID_PAL, INDEX_ID_GD, INDEX_ID_NT, + INDEX_ID_SO, INDEX_ID_MSK, INDEX_ID_IM, INDEX_ID_MC, @@ -792,7 +793,6 @@ enum { INDEX_ID_LA, INDEX_ID_CA, INDEX_ID_TXT, - INDEX_ID_SO, INDEX_ID_SPK, INDEX_ID_LP, INDEX_ID_OB, -- cgit v1.2.3 From 55683fafc5d5b7323fbcfe392e1f24f773a788a7 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Wed, 24 Mar 2021 11:38:22 +0100 Subject: Tweak default ID types processing order, step 13: Texts. Move Texts type toward the end of the list, this is a file-wrapper type with typically no dependency to any other ID. --- source/blender/makesdna/DNA_ID.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/makesdna/DNA_ID.h b/source/blender/makesdna/DNA_ID.h index 4c447045d8a..599ddf6af7c 100644 --- a/source/blender/makesdna/DNA_ID.h +++ b/source/blender/makesdna/DNA_ID.h @@ -770,6 +770,7 @@ enum { INDEX_ID_PAL, INDEX_ID_GD, INDEX_ID_NT, + INDEX_ID_TXT, INDEX_ID_SO, INDEX_ID_MSK, INDEX_ID_IM, @@ -792,7 +793,6 @@ enum { INDEX_ID_LT, INDEX_ID_LA, INDEX_ID_CA, - INDEX_ID_TXT, INDEX_ID_SPK, INDEX_ID_LP, INDEX_ID_OB, -- cgit v1.2.3 From 7db4ef98fe467fa1b2b0814fdb1d0369d92bdacd Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Wed, 24 Mar 2021 11:39:45 +0100 Subject: Tweak default ID types processing order, step 14: Fonts. Move Font type toward the end of the list, this is a file-wrapper type with typically no dependency to any other ID. --- source/blender/makesdna/DNA_ID.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/makesdna/DNA_ID.h b/source/blender/makesdna/DNA_ID.h index 599ddf6af7c..aa81f7e2b54 100644 --- a/source/blender/makesdna/DNA_ID.h +++ b/source/blender/makesdna/DNA_ID.h @@ -770,6 +770,7 @@ enum { INDEX_ID_PAL, INDEX_ID_GD, INDEX_ID_NT, + INDEX_ID_VF, INDEX_ID_TXT, INDEX_ID_SO, INDEX_ID_MSK, @@ -782,7 +783,6 @@ enum { INDEX_ID_CF, INDEX_ID_SIM, INDEX_ID_PA, - INDEX_ID_VF, INDEX_ID_AR, INDEX_ID_ME, INDEX_ID_CU, -- cgit v1.2.3 From fd75a4cbd83a0fccea019e0cb53eee5a21d0073e Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Wed, 24 Mar 2021 11:41:37 +0100 Subject: Tweak default ID types processing order, step 15: ShapeKeys. Move shape keys type just after all obdata types, since they are some sort of sub-geometry data. --- source/blender/makesdna/DNA_ID.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/makesdna/DNA_ID.h b/source/blender/makesdna/DNA_ID.h index aa81f7e2b54..8f4718b56cd 100644 --- a/source/blender/makesdna/DNA_ID.h +++ b/source/blender/makesdna/DNA_ID.h @@ -766,7 +766,6 @@ enum { INDEX_ID_LI = 0, INDEX_ID_IP, INDEX_ID_AC, - INDEX_ID_KE, INDEX_ID_PAL, INDEX_ID_GD, INDEX_ID_NT, @@ -783,6 +782,7 @@ enum { INDEX_ID_CF, INDEX_ID_SIM, INDEX_ID_PA, + INDEX_ID_KE, INDEX_ID_AR, INDEX_ID_ME, INDEX_ID_CU, -- cgit v1.2.3 From c1f5a56e32f2cac5dbb0a5b1515d809db1e01205 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Wed, 24 Mar 2021 12:52:51 +0100 Subject: Tweak default ID types processing order, step 16: Palettes. Move Palettes type with other weird, not-really-data ID types (like PaintCurves and Brushes), higher in the process. Those preset-like types may use a lot of other ID types, but should only be used by UI-related (and Scene, for tool settings) types. --- source/blender/makesdna/DNA_ID.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/makesdna/DNA_ID.h b/source/blender/makesdna/DNA_ID.h index 8f4718b56cd..f30a241439a 100644 --- a/source/blender/makesdna/DNA_ID.h +++ b/source/blender/makesdna/DNA_ID.h @@ -766,7 +766,6 @@ enum { INDEX_ID_LI = 0, INDEX_ID_IP, INDEX_ID_AC, - INDEX_ID_PAL, INDEX_ID_GD, INDEX_ID_NT, INDEX_ID_VF, @@ -797,6 +796,7 @@ enum { INDEX_ID_LP, INDEX_ID_OB, INDEX_ID_GR, + INDEX_ID_PAL, INDEX_ID_PC, INDEX_ID_BR, INDEX_ID_SCE, -- cgit v1.2.3 From 8bbd6ba8b738309ed49160691797e1bb2bdd3321 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Wed, 24 Mar 2021 15:07:58 +0100 Subject: Tweak default ID types processing order, step 17: Group and comments. Regroup ID type indices by categories, and add some comments about reasoning of current order. --- source/blender/makesdna/DNA_ID.h | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/source/blender/makesdna/DNA_ID.h b/source/blender/makesdna/DNA_ID.h index f30a241439a..d88db091cc2 100644 --- a/source/blender/makesdna/DNA_ID.h +++ b/source/blender/makesdna/DNA_ID.h @@ -763,25 +763,52 @@ typedef enum IDRecalcFlag { * See e.g. how #BKE_library_unused_linked_data_set_tag is doing this. */ enum { + /* Special case: Library, should never ever depend on any other type. */ INDEX_ID_LI = 0, - INDEX_ID_IP, + + /* Animation types, might be used by almost all other types. */ + INDEX_ID_IP, /* Deprecated. */ INDEX_ID_AC, + + /* Grease Pencil, special case, should be with the other obdata, but it can also be used by many + * other ID types, including node trees e.g. + * So there is no proper place for those, for now keep close to the lower end of the processing + * hierarchy, but we may want to re-evaluate that at some point. */ INDEX_ID_GD, + + /* Node trees, abstraction for procedural data, potentially used by many other ID types. + * + * NOTE: While node trees can also use many other ID types, they should not /own/ any of those, + * while they are being owned by many other ID types. This is why they are placed here. */ INDEX_ID_NT, + + /* File-wrapper types, those usually 'embed' external files in Blender, with no dependencies to + * other ID types. */ INDEX_ID_VF, INDEX_ID_TXT, INDEX_ID_SO, + + /* Image/movie types, can be used by shading ID types, but also directly by Objects, Scenes, etc. + */ INDEX_ID_MSK, INDEX_ID_IM, INDEX_ID_MC, + + /* Shading types. */ INDEX_ID_TE, INDEX_ID_MA, INDEX_ID_LS, INDEX_ID_WO, + + /* Simulation-related types. */ INDEX_ID_CF, INDEX_ID_SIM, INDEX_ID_PA, + + /* Shape Keys snow-flake, can be used by several obdata types. */ INDEX_ID_KE, + + /* Object data types. */ INDEX_ID_AR, INDEX_ID_ME, INDEX_ID_CU, @@ -794,15 +821,26 @@ enum { INDEX_ID_CA, INDEX_ID_SPK, INDEX_ID_LP, + + /* Collection and object types. */ INDEX_ID_OB, INDEX_ID_GR, + + /* Preset-like, not-really-data types, can use many other ID types but should never be used by + * any actual data type (besides Scene, due to tool settings). */ INDEX_ID_PAL, INDEX_ID_PC, INDEX_ID_BR, + + /* Scene, after preset-like ID types because of tool settings. */ INDEX_ID_SCE, + + /* UI-related types, should never be used by any other data type. */ INDEX_ID_SCR, INDEX_ID_WS, INDEX_ID_WM, + + /* Special values. */ INDEX_ID_NULL, INDEX_ID_MAX, }; -- cgit v1.2.3 From 337a122e44a3f6b28b8118c9fc4f06e397885c13 Mon Sep 17 00:00:00 2001 From: Charlie Jolly Date: Wed, 24 Mar 2021 13:57:27 +0000 Subject: Fix T86722: Missing updates after geometry nodes drag and drop Add missing call to ED_node_tag_update_nodetree which solves the missing update on initial drag. Reviewed By: HooglyBoogly Differential Revision: https://developer.blender.org/D10769 --- source/blender/editors/space_node/node_add.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/source/blender/editors/space_node/node_add.c b/source/blender/editors/space_node/node_add.c index a646804e0fd..8369f3e9258 100644 --- a/source/blender/editors/space_node/node_add.c +++ b/source/blender/editors/space_node/node_add.c @@ -472,6 +472,8 @@ static int node_add_object_exec(bContext *C, wmOperator *op) snode_notify(C, snode); snode_dag_update(C, snode); + ED_node_tag_update_nodetree(bmain, ntree, object_node); + return OPERATOR_FINISHED; } @@ -568,6 +570,8 @@ static int node_add_texture_exec(bContext *C, wmOperator *op) snode_notify(C, snode); snode_dag_update(C, snode); + ED_node_tag_update_nodetree(bmain, ntree, texture_node); + return OPERATOR_FINISHED; } @@ -670,6 +674,8 @@ static int node_add_collection_exec(bContext *C, wmOperator *op) snode_notify(C, snode); snode_dag_update(C, snode); + ED_node_tag_update_nodetree(bmain, ntree, collection_node); + return OPERATOR_FINISHED; } -- cgit v1.2.3 From ce359da5b3ac98c9f7cfd6593cc1769ec3b7247b Mon Sep 17 00:00:00 2001 From: Charlie Jolly Date: Wed, 24 Mar 2021 14:04:29 +0000 Subject: Fix T86894: Geometry nodes drag and drop creates a duplicate node Added a condition to the poll so that it ignores drag and drop on the button. The Paste Name operator is just not implemented. Doesn't work for shading nodes either. Reviewed By: HooglyBoogly Differential Revision: https://developer.blender.org/D10769 --- source/blender/editors/space_node/node_add.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/source/blender/editors/space_node/node_add.c b/source/blender/editors/space_node/node_add.c index 8369f3e9258..c4fe9e9e531 100644 --- a/source/blender/editors/space_node/node_add.c +++ b/source/blender/editors/space_node/node_add.c @@ -498,7 +498,8 @@ static int node_add_object_invoke(bContext *C, wmOperator *op, const wmEvent *ev static bool node_add_object_poll(bContext *C) { const SpaceNode *snode = CTX_wm_space_node(C); - return ED_operator_node_editable(C) && ELEM(snode->nodetree->type, NTREE_GEOMETRY); + return ED_operator_node_editable(C) && ELEM(snode->nodetree->type, NTREE_GEOMETRY) && + !UI_but_active_drop_name(C); } void NODE_OT_add_object(wmOperatorType *ot) @@ -596,7 +597,8 @@ static int node_add_texture_invoke(bContext *C, wmOperator *op, const wmEvent *e static bool node_add_texture_poll(bContext *C) { const SpaceNode *snode = CTX_wm_space_node(C); - return ED_operator_node_editable(C) && ELEM(snode->nodetree->type, NTREE_GEOMETRY); + return ED_operator_node_editable(C) && ELEM(snode->nodetree->type, NTREE_GEOMETRY) && + !UI_but_active_drop_name(C); } void NODE_OT_add_texture(wmOperatorType *ot) @@ -700,7 +702,8 @@ static int node_add_collection_invoke(bContext *C, wmOperator *op, const wmEvent static bool node_add_collection_poll(bContext *C) { const SpaceNode *snode = CTX_wm_space_node(C); - return ED_operator_node_editable(C) && ELEM(snode->nodetree->type, NTREE_GEOMETRY); + return ED_operator_node_editable(C) && ELEM(snode->nodetree->type, NTREE_GEOMETRY) && + !UI_but_active_drop_name(C); } void NODE_OT_add_collection(wmOperatorType *ot) -- cgit v1.2.3 From a8a92cd15a5251377474fbfdcf9ff0298a8457a9 Mon Sep 17 00:00:00 2001 From: Antonio Vazquez Date: Wed, 24 Mar 2021 15:14:43 +0100 Subject: 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 --- release/scripts/startup/bl_ui/space_topbar.py | 9 + source/blender/blenkernel/BKE_gpencil_geom.h | 16 +- source/blender/blenkernel/intern/gpencil_geom.c | 553 ++++ source/blender/editors/gpencil/gpencil_utils.c | 15 + source/blender/editors/include/ED_gpencil.h | 1 + source/blender/editors/io/CMakeLists.txt | 15 + source/blender/editors/io/io_gpencil.h | 45 + source/blender/editors/io/io_gpencil_export.c | 430 +++ source/blender/editors/io/io_gpencil_import.c | 195 ++ source/blender/editors/io/io_gpencil_utils.c | 64 + source/blender/editors/io/io_ops.c | 11 + source/blender/editors/space_file/filelist.c | 2 +- source/blender/io/CMakeLists.txt | 2 + source/blender/io/gpencil/CMakeLists.txt | 99 + source/blender/io/gpencil/gpencil_io.h | 92 + .../blender/io/gpencil/intern/gpencil_io_base.cc | 386 +++ source/blender/io/gpencil/intern/gpencil_io_base.h | 116 + .../blender/io/gpencil/intern/gpencil_io_capi.cc | 202 ++ .../io/gpencil/intern/gpencil_io_export_base.h | 38 + .../io/gpencil/intern/gpencil_io_export_pdf.cc | 311 ++ .../io/gpencil/intern/gpencil_io_export_pdf.h | 67 + .../io/gpencil/intern/gpencil_io_export_svg.cc | 464 +++ .../io/gpencil/intern/gpencil_io_export_svg.h | 89 + .../io/gpencil/intern/gpencil_io_import_base.cc | 85 + .../io/gpencil/intern/gpencil_io_import_base.h | 41 + .../io/gpencil/intern/gpencil_io_import_svg.cc | 253 ++ .../io/gpencil/intern/gpencil_io_import_svg.h | 56 + source/blender/io/gpencil/nanosvg/nanosvg.h | 3313 ++++++++++++++++++++ source/blender/python/intern/CMakeLists.txt | 8 + .../blender/python/intern/bpy_app_build_options.c | 14 + 30 files changed, 6990 insertions(+), 2 deletions(-) create mode 100644 source/blender/editors/io/io_gpencil.h create mode 100644 source/blender/editors/io/io_gpencil_export.c create mode 100644 source/blender/editors/io/io_gpencil_import.c create mode 100644 source/blender/editors/io/io_gpencil_utils.c create mode 100644 source/blender/io/gpencil/CMakeLists.txt create mode 100644 source/blender/io/gpencil/gpencil_io.h create mode 100644 source/blender/io/gpencil/intern/gpencil_io_base.cc create mode 100644 source/blender/io/gpencil/intern/gpencil_io_base.h create mode 100644 source/blender/io/gpencil/intern/gpencil_io_capi.cc create mode 100644 source/blender/io/gpencil/intern/gpencil_io_export_base.h create mode 100644 source/blender/io/gpencil/intern/gpencil_io_export_pdf.cc create mode 100644 source/blender/io/gpencil/intern/gpencil_io_export_pdf.h create mode 100644 source/blender/io/gpencil/intern/gpencil_io_export_svg.cc create mode 100644 source/blender/io/gpencil/intern/gpencil_io_export_svg.h create mode 100644 source/blender/io/gpencil/intern/gpencil_io_import_base.cc create mode 100644 source/blender/io/gpencil/intern/gpencil_io_import_base.h create mode 100644 source/blender/io/gpencil/intern/gpencil_io_import_svg.cc create mode 100644 source/blender/io/gpencil/intern/gpencil_io_import_svg.h create mode 100644 source/blender/io/gpencil/nanosvg/nanosvg.h diff --git a/release/scripts/startup/bl_ui/space_topbar.py b/release/scripts/startup/bl_ui/space_topbar.py index 7219922c379..adab0b0c88a 100644 --- a/release/scripts/startup/bl_ui/space_topbar.py +++ b/release/scripts/startup/bl_ui/space_topbar.py @@ -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" diff --git a/source/blender/blenkernel/BKE_gpencil_geom.h b/source/blender/blenkernel/BKE_gpencil_geom.h index 89a794f2df3..a9bd0a524c4 100644 --- a/source/blender/blenkernel/BKE_gpencil_geom.h +++ b/source/blender/blenkernel/BKE_gpencil_geom.h @@ -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 diff --git a/source/blender/blenkernel/intern/gpencil_geom.c b/source/blender/blenkernel/intern/gpencil_geom.c index 8c4882854d1..5d8dd99b3ae 100644 --- a/source/blender/blenkernel/intern/gpencil_geom.c +++ b/source/blender/blenkernel/intern/gpencil_geom.c @@ -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; +} /** \} */ diff --git a/source/blender/editors/gpencil/gpencil_utils.c b/source/blender/editors/gpencil/gpencil_utils.c index 5c041134a74..574670de7ca 100644 --- a/source/blender/editors/gpencil/gpencil_utils.c +++ b/source/blender/editors/gpencil/gpencil_utils.c @@ -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 */ diff --git a/source/blender/editors/include/ED_gpencil.h b/source/blender/editors/include/ED_gpencil.h index f3b5abb1072..e9ac21f60cf 100644 --- a/source/blender/editors/include/ED_gpencil.h +++ b/source/blender/editors/include/ED_gpencil.h @@ -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 ----------------- */ diff --git a/source/blender/editors/io/CMakeLists.txt b/source/blender/editors/io/CMakeLists.txt index e7effd05d34..d45c7ca9b75 100644 --- a/source/blender/editors/io/CMakeLists.txt +++ b/source/blender/editors/io/CMakeLists.txt @@ -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}") diff --git a/source/blender/editors/io/io_gpencil.h b/source/blender/editors/io/io_gpencil.h new file mode 100644 index 00000000000..98cb8b13310 --- /dev/null +++ b/source/blender/editors/io/io_gpencil.h @@ -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__ */ diff --git a/source/blender/editors/io/io_gpencil_export.c b/source/blender/editors/io/io_gpencil_export.c new file mode 100644 index 00000000000..6f1e503403b --- /dev/null +++ b/source/blender/editors/io/io_gpencil_export.c @@ -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, ¶ms); + 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, ¶ms); + 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 diff --git a/source/blender/editors/io/io_gpencil_import.c b/source/blender/editors/io/io_gpencil_import.c new file mode 100644 index 00000000000..9768da85940 --- /dev/null +++ b/source/blender/editors/io/io_gpencil_import.c @@ -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, ¶ms); + 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); +} diff --git a/source/blender/editors/io/io_gpencil_utils.c b/source/blender/editors/io/io_gpencil_utils.c new file mode 100644 index 00000000000..259a669519a --- /dev/null +++ b/source/blender/editors/io/io_gpencil_utils.c @@ -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; +} diff --git a/source/blender/editors/io/io_ops.c b/source/blender/editors/io/io_ops.c index acb511a414d..9fa34a1c55d 100644 --- a/source/blender/editors/io/io_ops.c +++ b/source/blender/editors/io/io_ops.c @@ -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); } diff --git a/source/blender/editors/space_file/filelist.c b/source/blender/editors/space_file/filelist.c index f5ec9a0e8a1..4c9f80bfa64 100644 --- a/source/blender/editors/space_file/filelist.c +++ b/source/blender/editors/space_file/filelist.c @@ -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)) { diff --git a/source/blender/io/CMakeLists.txt b/source/blender/io/CMakeLists.txt index 360cacc4360..f11ad7627b9 100644 --- a/source/blender/io/CMakeLists.txt +++ b/source/blender/io/CMakeLists.txt @@ -35,3 +35,5 @@ endif() if(WITH_USD) add_subdirectory(usd) endif() + +add_subdirectory(gpencil) diff --git a/source/blender/io/gpencil/CMakeLists.txt b/source/blender/io/gpencil/CMakeLists.txt new file mode 100644 index 00000000000..f394075cb9a --- /dev/null +++ b/source/blender/io/gpencil/CMakeLists.txt @@ -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}") diff --git a/source/blender/io/gpencil/gpencil_io.h b/source/blender/io/gpencil/gpencil_io.h new file mode 100644 index 00000000000..f4b2e59f8c5 --- /dev/null +++ b/source/blender/io/gpencil/gpencil_io.h @@ -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 diff --git a/source/blender/io/gpencil/intern/gpencil_io_base.cc b/source/blender/io/gpencil/intern/gpencil_io_base.cc new file mode 100644 index 00000000000..855252e648c --- /dev/null +++ b/source/blender/io/gpencil/intern/gpencil_io_base.cc @@ -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(¶ms); + BKE_camera_params_from_object(¶ms, cam_ob); + + /* Compute matrix, viewplane, .. */ + RenderData *rd = &scene_->r; + BKE_camera_params_compute_viewplane(¶ms, rd->xsch, rd->ysch, rd->xasp, rd->yasp); + BKE_camera_params_compute_matrix(¶ms); + + 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 diff --git a/source/blender/io/gpencil/intern/gpencil_io_base.h b/source/blender/io/gpencil/intern/gpencil_io_base.h new file mode 100644 index 00000000000..986221618b7 --- /dev/null +++ b/source/blender/io/gpencil/intern/gpencil_io_base.h @@ -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 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 diff --git a/source/blender/io/gpencil/intern/gpencil_io_capi.cc b/source/blender/io/gpencil/intern/gpencil_io_capi.cc new file mode 100644 index 00000000000..34539a66fe8 --- /dev/null +++ b/source/blender/io/gpencil/intern/gpencil_io_capi.cc @@ -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 + +#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; +} diff --git a/source/blender/io/gpencil/intern/gpencil_io_export_base.h b/source/blender/io/gpencil/intern/gpencil_io_export_base.h new file mode 100644 index 00000000000..19a24a75fd2 --- /dev/null +++ b/source/blender/io/gpencil/intern/gpencil_io_export_base.h @@ -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 diff --git a/source/blender/io/gpencil/intern/gpencil_io_export_pdf.cc b/source/blender/io/gpencil/intern/gpencil_io_export_pdf.cc new file mode 100644 index 00000000000..ba16d635c2d --- /dev/null +++ b/source/blender/io/gpencil/intern/gpencil_io_export_pdf.cc @@ -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 diff --git a/source/blender/io/gpencil/intern/gpencil_io_export_pdf.h b/source/blender/io/gpencil/intern/gpencil_io_export_pdf.h new file mode 100644 index 00000000000..009c05a8b49 --- /dev/null +++ b/source/blender/io/gpencil/intern/gpencil_io_export_pdf.h @@ -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 diff --git a/source/blender/io/gpencil/intern/gpencil_io_export_svg.cc b/source/blender/io/gpencil/intern/gpencil_io_export_svg.cc new file mode 100644 index 00000000000..89584cd242f --- /dev/null +++ b/source/blender/io/gpencil/intern/gpencil_io_export_svg.cc @@ -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 diff --git a/source/blender/io/gpencil/intern/gpencil_io_export_svg.h b/source/blender/io/gpencil/intern/gpencil_io_export_svg.h new file mode 100644 index 00000000000..f564736c16e --- /dev/null +++ b/source/blender/io/gpencil/intern/gpencil_io_export_svg.h @@ -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 diff --git a/source/blender/io/gpencil/intern/gpencil_io_import_base.cc b/source/blender/io/gpencil/intern/gpencil_io_import_base.cc new file mode 100644 index 00000000000..b49b4c969ad --- /dev/null +++ b/source/blender/io/gpencil/intern/gpencil_io_import_base.cc @@ -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 diff --git a/source/blender/io/gpencil/intern/gpencil_io_import_base.h b/source/blender/io/gpencil/intern/gpencil_io_import_base.h new file mode 100644 index 00000000000..efe6264e4e9 --- /dev/null +++ b/source/blender/io/gpencil/intern/gpencil_io_import_base.h @@ -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 diff --git a/source/blender/io/gpencil/intern/gpencil_io_import_svg.cc b/source/blender/io/gpencil/intern/gpencil_io_import_svg.cc new file mode 100644 index 00000000000..7f450477ac2 --- /dev/null +++ b/source/blender/io/gpencil/intern/gpencil_io_import_svg.cc @@ -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 diff --git a/source/blender/io/gpencil/intern/gpencil_io_import_svg.h b/source/blender/io/gpencil/intern/gpencil_io_import_svg.h new file mode 100644 index 00000000000..6a34ec8423b --- /dev/null +++ b/source/blender/io/gpencil/intern/gpencil_io_import_svg.h @@ -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 diff --git a/source/blender/io/gpencil/nanosvg/nanosvg.h b/source/blender/io/gpencil/nanosvg/nanosvg.h new file mode 100644 index 00000000000..1009d684f7c --- /dev/null +++ b/source/blender/io/gpencil/nanosvg/nanosvg.h @@ -0,0 +1,3313 @@ +/* + * Copyright (c) 2013-14 Mikko Mononen memon@inside.org + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + * + * The SVG parser is based on Anti-Grain Geometry 2.4 SVG example + * Copyright (C) 2002-2004 Maxim Shemanarev (McSeem) (http://www.antigrain.com/) + * + * Arc calculation code based on canvg (https://code.google.com/p/canvg/) + * + * Bounding box calculation based on + * http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html + * + * This is a modified version for Blender used by importers. + * + */ + +#ifndef NANOSVG_H +#define NANOSVG_H + +#ifndef NANOSVG_CPLUSPLUS +# ifdef __cplusplus +extern "C" { +# endif +#endif + +// NanoSVG is a simple stupid single-header-file SVG parse. The output of the parser is a list of +// cubic bezier shapes. +// +// The library suits well for anything from rendering scalable icons in your editor application to +// prototyping a game. +// +// NanoSVG supports a wide range of SVG features, but something may be missing, feel free to create +// a pull request! +// +// The shapes in the SVG images are transformed by the viewBox and converted to specified units. +// That is, you should get the same looking data as your designed in your favorite app. +// +// NanoSVG can return the paths in few different units. For example if you want to render an image, +// you may choose to get the paths in pixels, or if you are feeding the data into a CNC-cutter, you +// may want to use millimeters. +// +// The units passed to NanoSVG should be one of: 'px', 'pt', 'pc' 'mm', 'cm', or 'in'. +// DPI (dots-per-inch) controls how the unit conversion is done. +// +// If you don't know or care about the units stuff, "px" and 96 should get you going. + +/* Example Usage: + // Load SVG + NSVGimage* image; + image = nsvgParseFromFile("test.svg", "px", 96); + printf("size: %f x %f\n", image->width, image->height); + // Use... + for (NSVGshape *shape = image->shapes; shape != NULL; shape = shape->next) { + for (NSVGpath *path = shape->paths; path != NULL; path = path->next) { + for (int i = 0; i < path->npts-1; i += 3) { + float* p = &path->pts[i*2]; + drawCubicBez(p[0],p[1], p[2],p[3], p[4],p[5], p[6],p[7]); + } + } + } + // Delete + nsvgDelete(image); +*/ + +enum NSVGpaintType { + NSVG_PAINT_NONE = 0, + NSVG_PAINT_COLOR = 1, + NSVG_PAINT_LINEAR_GRADIENT = 2, + NSVG_PAINT_RADIAL_GRADIENT = 3 +}; + +enum NSVGspreadType { NSVG_SPREAD_PAD = 0, NSVG_SPREAD_REFLECT = 1, NSVG_SPREAD_REPEAT = 2 }; + +enum NSVGlineJoin { NSVG_JOIN_MITER = 0, NSVG_JOIN_ROUND = 1, NSVG_JOIN_BEVEL = 2 }; + +enum NSVGlineCap { NSVG_CAP_BUTT = 0, NSVG_CAP_ROUND = 1, NSVG_CAP_SQUARE = 2 }; + +enum NSVGfillRule { NSVG_FILLRULE_NONZERO = 0, NSVG_FILLRULE_EVENODD = 1 }; + +enum NSVGflags { NSVG_FLAGS_VISIBLE = 0x01 }; + +typedef struct NSVGgradientStop { + unsigned int color; + float offset; +} NSVGgradientStop; + +typedef struct NSVGgradient { + float xform[6]; + char spread; + float fx, fy; + int nstops; + NSVGgradientStop stops[1]; +} NSVGgradient; + +typedef struct NSVGpaint { + char type; + union { + unsigned int color; + NSVGgradient *gradient; + }; +} NSVGpaint; + +typedef struct NSVGpath { + float *pts; // Cubic bezier points: x0,y0, [cpx1,cpx1,cpx2,cpy2,x1,y1], ... + int npts; // Total number of bezier points. + char closed; // Flag indicating if shapes should be treated as closed. + float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy]. + struct NSVGpath *next; // Pointer to next path, or NULL if last element. +} NSVGpath; + +typedef struct NSVGshape { + char id[64]; // Optional 'id' attr of the shape or its group + /* Blender: Parent ID used for layer creation. */ + char id_parent[64]; + NSVGpaint fill; // Fill paint + NSVGpaint stroke; // Stroke paint + float opacity; // Opacity of the shape. + float strokeWidth; // Stroke width (scaled). + float strokeDashOffset; // Stroke dash offset (scaled). + float strokeDashArray[8]; // Stroke dash array (scaled). + char strokeDashCount; // Number of dash values in dash array. + char strokeLineJoin; // Stroke join type. + char strokeLineCap; // Stroke cap type. + float miterLimit; // Miter limit + char fillRule; // Fill rule, see NSVGfillRule. + unsigned char flags; // Logical or of NSVG_FLAGS_* flags + float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy]. + NSVGpath *paths; // Linked list of paths in the image. + struct NSVGshape *next; // Pointer to next shape, or NULL if last element. +} NSVGshape; + +typedef struct NSVGimage { + float width; // Width of the image. + float height; // Height of the image. + NSVGshape *shapes; // Linked list of shapes in the image. +} NSVGimage; + +// Parses SVG file from a file, returns SVG image as paths. +NSVGimage *nsvgParseFromFile(const char *filename, const char *units, float dpi); + +// Parses SVG file from a null terminated string, returns SVG image as paths. +// Important note: changes the string. +NSVGimage *nsvgParse(char *input, const char *units, float dpi); + +// Duplicates a path. +NSVGpath *nsvgDuplicatePath(NSVGpath *p); + +// Deletes an image. +void nsvgDelete(NSVGimage *image); + +#ifndef NANOSVG_CPLUSPLUS +# ifdef __cplusplus +} +# endif +#endif + +#endif // NANOSVG_H + +#ifdef NANOSVG_IMPLEMENTATION + +#include +#include +#include + +#define NSVG_PI (3.14159265358979323846264338327f) +#define NSVG_KAPPA90 \ + (0.5522847493f) // Length proportional to radius of a cubic bezier handle for 90deg arcs. + +#define NSVG_ALIGN_MIN 0 +#define NSVG_ALIGN_MID 1 +#define NSVG_ALIGN_MAX 2 +#define NSVG_ALIGN_NONE 0 +#define NSVG_ALIGN_MEET 1 +#define NSVG_ALIGN_SLICE 2 + +#define NSVG_NOTUSED(v) \ + do { \ + (void)(1 ? (void)0 : ((void)(v))); \ + } while (0) +#define NSVG_RGB(r, g, b) (((unsigned int)r) | ((unsigned int)g << 8) | ((unsigned int)b << 16)) + +#ifdef _MSC_VER +# pragma warning(disable : 4996) // Switch off security warnings +# pragma warning(disable : 4100) // Switch off unreferenced formal parameter warnings +# ifdef __cplusplus +# define NSVG_INLINE inline +# else +# define NSVG_INLINE +# endif +#else +# define NSVG_INLINE inline +#endif + +static int nsvg__isspace(char c) +{ + return strchr(" \t\n\v\f\r", c) != 0; +} + +static int nsvg__isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static NSVG_INLINE float nsvg__minf(float a, float b) +{ + return a < b ? a : b; +} +static NSVG_INLINE float nsvg__maxf(float a, float b) +{ + return a > b ? a : b; +} + +// Simple XML parser + +#define NSVG_XML_TAG 1 +#define NSVG_XML_CONTENT 2 +#define NSVG_XML_MAX_ATTRIBS 256 + +static void nsvg__parseContent(char *s, void (*contentCb)(void *ud, const char *s), void *ud) +{ + // Trim start white spaces + while (*s && nsvg__isspace(*s)) + s++; + if (!*s) + return; + + if (contentCb) + (*contentCb)(ud, s); +} + +static void nsvg__parseElement(char *s, + void (*startelCb)(void *ud, const char *el, const char **attr), + void (*endelCb)(void *ud, const char *el), + void *ud) +{ + const char *attr[NSVG_XML_MAX_ATTRIBS]; + int nattr = 0; + char *name; + int start = 0; + int end = 0; + char quote; + + // Skip white space after the '<' + while (*s && nsvg__isspace(*s)) + s++; + + // Check if the tag is end tag + if (*s == '/') { + s++; + end = 1; + } + else { + start = 1; + } + + // Skip comments, data and preprocessor stuff. + if (!*s || *s == '?' || *s == '!') + return; + + // Get tag name + name = s; + while (*s && !nsvg__isspace(*s)) + s++; + if (*s) { + *s++ = '\0'; + } + + // Get attribs + while (!end && *s && nattr < NSVG_XML_MAX_ATTRIBS - 3) { + char *name = NULL; + char *value = NULL; + + // Skip white space before the attrib name + while (*s && nsvg__isspace(*s)) + s++; + if (!*s) + break; + if (*s == '/') { + end = 1; + break; + } + name = s; + // Find end of the attrib name. + while (*s && !nsvg__isspace(*s) && *s != '=') + s++; + if (*s) { + *s++ = '\0'; + } + // Skip until the beginning of the value. + while (*s && *s != '\"' && *s != '\'') + s++; + if (!*s) + break; + quote = *s; + s++; + // Store value and find the end of it. + value = s; + while (*s && *s != quote) + s++; + if (*s) { + *s++ = '\0'; + } + + // Store only well formed attributes + if (name && value) { + attr[nattr++] = name; + attr[nattr++] = value; + } + } + + // List terminator + attr[nattr++] = 0; + attr[nattr++] = 0; + + // Call callbacks. + if (start && startelCb) + (*startelCb)(ud, name, attr); + if (end && endelCb) + (*endelCb)(ud, name); +} + +static int nsvg__parseXML(char *input, + void (*startelCb)(void *ud, const char *el, const char **attr), + void (*endelCb)(void *ud, const char *el), + void (*contentCb)(void *ud, const char *s), + void *ud) +{ + char *s = input; + char *mark = s; + int state = NSVG_XML_CONTENT; + while (*s) { + if (*s == '<' && state == NSVG_XML_CONTENT) { + // Start of a tag + *s++ = '\0'; + nsvg__parseContent(mark, contentCb, ud); + mark = s; + state = NSVG_XML_TAG; + } + else if (*s == '>' && state == NSVG_XML_TAG) { + // Start of a content or new tag. + *s++ = '\0'; + nsvg__parseElement(mark, startelCb, endelCb, ud); + mark = s; + state = NSVG_XML_CONTENT; + } + else { + s++; + } + } + + return 1; +} + +/* Simple SVG parser. */ + +#define NSVG_MAX_ATTR 128 +#define NSVG_MAX_BREADCRUMB 5 + +enum NSVGgradientUnits { NSVG_USER_SPACE = 0, NSVG_OBJECT_SPACE = 1 }; + +#define NSVG_MAX_DASHES 8 + +enum NSVGunits { + NSVG_UNITS_USER, + NSVG_UNITS_PX, + NSVG_UNITS_PT, + NSVG_UNITS_PC, + NSVG_UNITS_MM, + NSVG_UNITS_CM, + NSVG_UNITS_IN, + NSVG_UNITS_PERCENT, + NSVG_UNITS_EM, + NSVG_UNITS_EX +}; + +typedef struct NSVGcoordinate { + float value; + int units; +} NSVGcoordinate; + +typedef struct NSVGlinearData { + NSVGcoordinate x1, y1, x2, y2; +} NSVGlinearData; + +typedef struct NSVGradialData { + NSVGcoordinate cx, cy, r, fx, fy; +} NSVGradialData; + +typedef struct NSVGgradientData { + char id[64]; + char ref[64]; + char type; + union { + NSVGlinearData linear; + NSVGradialData radial; + }; + char spread; + char units; + float xform[6]; + int nstops; + NSVGgradientStop *stops; + struct NSVGgradientData *next; +} NSVGgradientData; + +typedef struct NSVGattrib { + char id[64]; + float xform[6]; + unsigned int fillColor; + unsigned int strokeColor; + float opacity; + float fillOpacity; + float strokeOpacity; + char fillGradient[64]; + char strokeGradient[64]; + float strokeWidth; + float strokeDashOffset; + float strokeDashArray[NSVG_MAX_DASHES]; + int strokeDashCount; + char strokeLineJoin; + char strokeLineCap; + float miterLimit; + char fillRule; + float fontSize; + unsigned int stopColor; + float stopOpacity; + float stopOffset; + char hasFill; + char hasStroke; + char visible; +} NSVGattrib; + +typedef struct NSVGparser { + NSVGattrib attr[NSVG_MAX_ATTR]; + int attrHead; + float *pts; + int npts; + int cpts; + NSVGpath *plist; + NSVGimage *image; + NSVGgradientData *gradients; + NSVGshape *shapesTail; + float viewMinx, viewMiny, viewWidth, viewHeight; + int alignX, alignY, alignType; + float dpi; + char pathFlag; + char defsFlag; + /** Blender breadscrum for layers. */ + char breadcrumb[NSVG_MAX_BREADCRUMB][64]; + /** Blender number of elements in breadscrum. */ + int breadcrumb_len; +} NSVGparser; + +static void nsvg__xformIdentity(float *t) +{ + t[0] = 1.0f; + t[1] = 0.0f; + t[2] = 0.0f; + t[3] = 1.0f; + t[4] = 0.0f; + t[5] = 0.0f; +} + +static void nsvg__xformSetTranslation(float *t, float tx, float ty) +{ + t[0] = 1.0f; + t[1] = 0.0f; + t[2] = 0.0f; + t[3] = 1.0f; + t[4] = tx; + t[5] = ty; +} + +static void nsvg__xformSetScale(float *t, float sx, float sy) +{ + t[0] = sx; + t[1] = 0.0f; + t[2] = 0.0f; + t[3] = sy; + t[4] = 0.0f; + t[5] = 0.0f; +} + +static void nsvg__xformSetSkewX(float *t, float a) +{ + t[0] = 1.0f; + t[1] = 0.0f; + t[2] = tanf(a); + t[3] = 1.0f; + t[4] = 0.0f; + t[5] = 0.0f; +} + +static void nsvg__xformSetSkewY(float *t, float a) +{ + t[0] = 1.0f; + t[1] = tanf(a); + t[2] = 0.0f; + t[3] = 1.0f; + t[4] = 0.0f; + t[5] = 0.0f; +} + +static void nsvg__xformSetRotation(float *t, float a) +{ + float cs = cosf(a), sn = sinf(a); + t[0] = cs; + t[1] = sn; + t[2] = -sn; + t[3] = cs; + t[4] = 0.0f; + t[5] = 0.0f; +} + +static void nsvg__xformMultiply(float *t, float *s) +{ + float t0 = t[0] * s[0] + t[1] * s[2]; + float t2 = t[2] * s[0] + t[3] * s[2]; + float t4 = t[4] * s[0] + t[5] * s[2] + s[4]; + t[1] = t[0] * s[1] + t[1] * s[3]; + t[3] = t[2] * s[1] + t[3] * s[3]; + t[5] = t[4] * s[1] + t[5] * s[3] + s[5]; + t[0] = t0; + t[2] = t2; + t[4] = t4; +} + +static void nsvg__xformInverse(float *inv, float *t) +{ + double invdet, det = (double)t[0] * t[3] - (double)t[2] * t[1]; + if (det > -1e-6 && det < 1e-6) { + nsvg__xformIdentity(t); + return; + } + invdet = 1.0 / det; + inv[0] = (float)(t[3] * invdet); + inv[2] = (float)(-t[2] * invdet); + inv[4] = (float)(((double)t[2] * t[5] - (double)t[3] * t[4]) * invdet); + inv[1] = (float)(-t[1] * invdet); + inv[3] = (float)(t[0] * invdet); + inv[5] = (float)(((double)t[1] * t[4] - (double)t[0] * t[5]) * invdet); +} + +static void nsvg__xformPremultiply(float *t, float *s) +{ + float s2[6]; + memcpy(s2, s, sizeof(float) * 6); + nsvg__xformMultiply(s2, t); + memcpy(t, s2, sizeof(float) * 6); +} + +static void nsvg__xformPoint(float *dx, float *dy, float x, float y, float *t) +{ + *dx = x * t[0] + y * t[2] + t[4]; + *dy = x * t[1] + y * t[3] + t[5]; +} + +static void nsvg__xformVec(float *dx, float *dy, float x, float y, float *t) +{ + *dx = x * t[0] + y * t[2]; + *dy = x * t[1] + y * t[3]; +} + +#define NSVG_EPSILON (1e-12) + +static int nsvg__ptInBounds(float *pt, float *bounds) +{ + return pt[0] >= bounds[0] && pt[0] <= bounds[2] && pt[1] >= bounds[1] && pt[1] <= bounds[3]; +} + +static double nsvg__evalBezier(double t, double p0, double p1, double p2, double p3) +{ + double it = 1.0 - t; + return it * it * it * p0 + 3.0 * it * it * t * p1 + 3.0 * it * t * t * p2 + t * t * t * p3; +} + +static void nsvg__curveBounds(float *bounds, float *curve) +{ + int i, j, count; + double roots[2], a, b, c, b2ac, t, v; + float *v0 = &curve[0]; + float *v1 = &curve[2]; + float *v2 = &curve[4]; + float *v3 = &curve[6]; + + // Start the bounding box by end points + bounds[0] = nsvg__minf(v0[0], v3[0]); + bounds[1] = nsvg__minf(v0[1], v3[1]); + bounds[2] = nsvg__maxf(v0[0], v3[0]); + bounds[3] = nsvg__maxf(v0[1], v3[1]); + + // Bezier curve fits inside the convex hull of it's control points. + // If control points are inside the bounds, we're done. + if (nsvg__ptInBounds(v1, bounds) && nsvg__ptInBounds(v2, bounds)) + return; + + // Add bezier curve inflection points in X and Y. + for (i = 0; i < 2; i++) { + a = -3.0 * v0[i] + 9.0 * v1[i] - 9.0 * v2[i] + 3.0 * v3[i]; + b = 6.0 * v0[i] - 12.0 * v1[i] + 6.0 * v2[i]; + c = 3.0 * v1[i] - 3.0 * v0[i]; + count = 0; + if (fabs(a) < NSVG_EPSILON) { + if (fabs(b) > NSVG_EPSILON) { + t = -c / b; + if (t > NSVG_EPSILON && t < 1.0 - NSVG_EPSILON) + roots[count++] = t; + } + } + else { + b2ac = b * b - 4.0 * c * a; + if (b2ac > NSVG_EPSILON) { + t = (-b + sqrt(b2ac)) / (2.0 * a); + if (t > NSVG_EPSILON && t < 1.0 - NSVG_EPSILON) + roots[count++] = t; + t = (-b - sqrt(b2ac)) / (2.0 * a); + if (t > NSVG_EPSILON && t < 1.0 - NSVG_EPSILON) + roots[count++] = t; + } + } + for (j = 0; j < count; j++) { + v = nsvg__evalBezier(roots[j], v0[i], v1[i], v2[i], v3[i]); + bounds[0 + i] = nsvg__minf(bounds[0 + i], (float)v); + bounds[2 + i] = nsvg__maxf(bounds[2 + i], (float)v); + } + } +} + +static NSVGparser *nsvg__createParser() +{ + NSVGparser *p; + p = (NSVGparser *)malloc(sizeof(NSVGparser)); + if (p == NULL) + goto error; + memset(p, 0, sizeof(NSVGparser)); + + p->image = (NSVGimage *)malloc(sizeof(NSVGimage)); + if (p->image == NULL) + goto error; + memset(p->image, 0, sizeof(NSVGimage)); + + // Init style + nsvg__xformIdentity(p->attr[0].xform); + memset(p->attr[0].id, 0, sizeof p->attr[0].id); + p->attr[0].fillColor = NSVG_RGB(0, 0, 0); + p->attr[0].strokeColor = NSVG_RGB(0, 0, 0); + p->attr[0].opacity = 1; + p->attr[0].fillOpacity = 1; + p->attr[0].strokeOpacity = 1; + p->attr[0].stopOpacity = 1; + p->attr[0].strokeWidth = 1; + p->attr[0].strokeLineJoin = NSVG_JOIN_MITER; + p->attr[0].strokeLineCap = NSVG_CAP_BUTT; + p->attr[0].miterLimit = 4; + p->attr[0].fillRule = NSVG_FILLRULE_NONZERO; + p->attr[0].hasFill = 1; + p->attr[0].visible = 1; + + return p; + +error: + if (p) { + if (p->image) + free(p->image); + free(p); + } + return NULL; +} + +static void nsvg__deletePaths(NSVGpath *path) +{ + while (path) { + NSVGpath *next = path->next; + if (path->pts != NULL) + free(path->pts); + free(path); + path = next; + } +} + +static void nsvg__deletePaint(NSVGpaint *paint) +{ + if (paint->type == NSVG_PAINT_LINEAR_GRADIENT || paint->type == NSVG_PAINT_RADIAL_GRADIENT) + free(paint->gradient); +} + +static void nsvg__deleteGradientData(NSVGgradientData *grad) +{ + NSVGgradientData *next; + while (grad != NULL) { + next = grad->next; + free(grad->stops); + free(grad); + grad = next; + } +} + +static void nsvg__deleteParser(NSVGparser *p) +{ + if (p != NULL) { + nsvg__deletePaths(p->plist); + nsvg__deleteGradientData(p->gradients); + nsvgDelete(p->image); + free(p->pts); + free(p); + } +} + +static void nsvg__resetPath(NSVGparser *p) +{ + p->npts = 0; +} + +static void nsvg__addPoint(NSVGparser *p, float x, float y) +{ + if (p->npts + 1 > p->cpts) { + p->cpts = p->cpts ? p->cpts * 2 : 8; + p->pts = (float *)realloc(p->pts, p->cpts * 2 * sizeof(float)); + if (!p->pts) + return; + } + p->pts[p->npts * 2 + 0] = x; + p->pts[p->npts * 2 + 1] = y; + p->npts++; +} + +static void nsvg__moveTo(NSVGparser *p, float x, float y) +{ + if (p->npts > 0) { + p->pts[(p->npts - 1) * 2 + 0] = x; + p->pts[(p->npts - 1) * 2 + 1] = y; + } + else { + nsvg__addPoint(p, x, y); + } +} + +static void nsvg__lineTo(NSVGparser *p, float x, float y) +{ + float px, py, dx, dy; + if (p->npts > 0) { + px = p->pts[(p->npts - 1) * 2 + 0]; + py = p->pts[(p->npts - 1) * 2 + 1]; + dx = x - px; + dy = y - py; + nsvg__addPoint(p, px + dx / 3.0f, py + dy / 3.0f); + nsvg__addPoint(p, x - dx / 3.0f, y - dy / 3.0f); + nsvg__addPoint(p, x, y); + } +} + +static void nsvg__cubicBezTo( + NSVGparser *p, float cpx1, float cpy1, float cpx2, float cpy2, float x, float y) +{ + if (p->npts > 0) { + nsvg__addPoint(p, cpx1, cpy1); + nsvg__addPoint(p, cpx2, cpy2); + nsvg__addPoint(p, x, y); + } +} + +static NSVGattrib *nsvg__getAttr(NSVGparser *p) +{ + return &p->attr[p->attrHead]; +} + +static void nsvg__pushAttr(NSVGparser *p) +{ + if (p->attrHead < NSVG_MAX_ATTR - 1) { + p->attrHead++; + memcpy(&p->attr[p->attrHead], &p->attr[p->attrHead - 1], sizeof(NSVGattrib)); + } +} + +static void nsvg__popAttr(NSVGparser *p) +{ + if (p->attrHead > 0) + p->attrHead--; +} + +static float nsvg__actualOrigX(NSVGparser *p) +{ + return p->viewMinx; +} + +static float nsvg__actualOrigY(NSVGparser *p) +{ + return p->viewMiny; +} + +static float nsvg__actualWidth(NSVGparser *p) +{ + return p->viewWidth; +} + +static float nsvg__actualHeight(NSVGparser *p) +{ + return p->viewHeight; +} + +static float nsvg__actualLength(NSVGparser *p) +{ + float w = nsvg__actualWidth(p), h = nsvg__actualHeight(p); + return sqrtf(w * w + h * h) / sqrtf(2.0f); +} + +static float nsvg__convertToPixels(NSVGparser *p, NSVGcoordinate c, float orig, float length) +{ + NSVGattrib *attr = nsvg__getAttr(p); + switch (c.units) { + case NSVG_UNITS_USER: + return c.value; + case NSVG_UNITS_PX: + return c.value; + case NSVG_UNITS_PT: + return c.value / 72.0f * p->dpi; + case NSVG_UNITS_PC: + return c.value / 6.0f * p->dpi; + case NSVG_UNITS_MM: + return c.value / 25.4f * p->dpi; + case NSVG_UNITS_CM: + return c.value / 2.54f * p->dpi; + case NSVG_UNITS_IN: + return c.value * p->dpi; + case NSVG_UNITS_EM: + return c.value * attr->fontSize; + case NSVG_UNITS_EX: + return c.value * attr->fontSize * 0.52f; // x-height of Helvetica. + case NSVG_UNITS_PERCENT: + return orig + c.value / 100.0f * length; + default: + return c.value; + } + return c.value; +} + +static NSVGgradientData *nsvg__findGradientData(NSVGparser *p, const char *id) +{ + NSVGgradientData *grad = p->gradients; + if (id == NULL || *id == '\0') + return NULL; + while (grad != NULL) { + if (strcmp(grad->id, id) == 0) + return grad; + grad = grad->next; + } + return NULL; +} + +static NSVGgradient *nsvg__createGradient(NSVGparser *p, + const char *id, + const float *localBounds, + char *paintType) +{ + NSVGattrib *attr = nsvg__getAttr(p); + NSVGgradientData *data = NULL; + NSVGgradientData *ref = NULL; + NSVGgradientStop *stops = NULL; + NSVGgradient *grad; + float ox, oy, sw, sh, sl; + int nstops = 0; + int refIter; + + data = nsvg__findGradientData(p, id); + if (data == NULL) + return NULL; + + // TODO: use ref to fill in all unset values too. + ref = data; + refIter = 0; + while (ref != NULL) { + NSVGgradientData *nextRef = NULL; + if (stops == NULL && ref->stops != NULL) { + stops = ref->stops; + nstops = ref->nstops; + break; + } + nextRef = nsvg__findGradientData(p, ref->ref); + if (nextRef == ref) + break; // prevent infite loops on malformed data + ref = nextRef; + refIter++; + if (refIter > 32) + break; // prevent infite loops on malformed data + } + if (stops == NULL) + return NULL; + + grad = (NSVGgradient *)malloc(sizeof(NSVGgradient) + sizeof(NSVGgradientStop) * (nstops - 1)); + if (grad == NULL) + return NULL; + + // The shape width and height. + if (data->units == NSVG_OBJECT_SPACE) { + ox = localBounds[0]; + oy = localBounds[1]; + sw = localBounds[2] - localBounds[0]; + sh = localBounds[3] - localBounds[1]; + } + else { + ox = nsvg__actualOrigX(p); + oy = nsvg__actualOrigY(p); + sw = nsvg__actualWidth(p); + sh = nsvg__actualHeight(p); + } + sl = sqrtf(sw * sw + sh * sh) / sqrtf(2.0f); + + if (data->type == NSVG_PAINT_LINEAR_GRADIENT) { + float x1, y1, x2, y2, dx, dy; + x1 = nsvg__convertToPixels(p, data->linear.x1, ox, sw); + y1 = nsvg__convertToPixels(p, data->linear.y1, oy, sh); + x2 = nsvg__convertToPixels(p, data->linear.x2, ox, sw); + y2 = nsvg__convertToPixels(p, data->linear.y2, oy, sh); + // Calculate transform aligned to the line + dx = x2 - x1; + dy = y2 - y1; + grad->xform[0] = dy; + grad->xform[1] = -dx; + grad->xform[2] = dx; + grad->xform[3] = dy; + grad->xform[4] = x1; + grad->xform[5] = y1; + } + else { + float cx, cy, fx, fy, r; + cx = nsvg__convertToPixels(p, data->radial.cx, ox, sw); + cy = nsvg__convertToPixels(p, data->radial.cy, oy, sh); + fx = nsvg__convertToPixels(p, data->radial.fx, ox, sw); + fy = nsvg__convertToPixels(p, data->radial.fy, oy, sh); + r = nsvg__convertToPixels(p, data->radial.r, 0, sl); + // Calculate transform aligned to the circle + grad->xform[0] = r; + grad->xform[1] = 0; + grad->xform[2] = 0; + grad->xform[3] = r; + grad->xform[4] = cx; + grad->xform[5] = cy; + grad->fx = fx / r; + grad->fy = fy / r; + } + + nsvg__xformMultiply(grad->xform, data->xform); + nsvg__xformMultiply(grad->xform, attr->xform); + + grad->spread = data->spread; + memcpy(grad->stops, stops, nstops * sizeof(NSVGgradientStop)); + grad->nstops = nstops; + + *paintType = data->type; + + return grad; +} + +static float nsvg__getAverageScale(float *t) +{ + float sx = sqrtf(t[0] * t[0] + t[2] * t[2]); + float sy = sqrtf(t[1] * t[1] + t[3] * t[3]); + return (sx + sy) * 0.5f; +} + +static void nsvg__getLocalBounds(float *bounds, NSVGshape *shape, float *xform) +{ + NSVGpath *path; + float curve[4 * 2], curveBounds[4]; + int i, first = 1; + for (path = shape->paths; path != NULL; path = path->next) { + nsvg__xformPoint(&curve[0], &curve[1], path->pts[0], path->pts[1], xform); + for (i = 0; i < path->npts - 1; i += 3) { + nsvg__xformPoint( + &curve[2], &curve[3], path->pts[(i + 1) * 2], path->pts[(i + 1) * 2 + 1], xform); + nsvg__xformPoint( + &curve[4], &curve[5], path->pts[(i + 2) * 2], path->pts[(i + 2) * 2 + 1], xform); + nsvg__xformPoint( + &curve[6], &curve[7], path->pts[(i + 3) * 2], path->pts[(i + 3) * 2 + 1], xform); + nsvg__curveBounds(curveBounds, curve); + if (first) { + bounds[0] = curveBounds[0]; + bounds[1] = curveBounds[1]; + bounds[2] = curveBounds[2]; + bounds[3] = curveBounds[3]; + first = 0; + } + else { + bounds[0] = nsvg__minf(bounds[0], curveBounds[0]); + bounds[1] = nsvg__minf(bounds[1], curveBounds[1]); + bounds[2] = nsvg__maxf(bounds[2], curveBounds[2]); + bounds[3] = nsvg__maxf(bounds[3], curveBounds[3]); + } + curve[0] = curve[6]; + curve[1] = curve[7]; + } + } +} + +static void nsvg__addShape(NSVGparser *p) +{ + NSVGattrib *attr = nsvg__getAttr(p); + float scale = 1.0f; + NSVGshape *shape; + NSVGpath *path; + int i; + + if (p->plist == NULL) + return; + + shape = (NSVGshape *)malloc(sizeof(NSVGshape)); + if (shape == NULL) + goto error; + memset(shape, 0, sizeof(NSVGshape)); + + memcpy(shape->id, attr->id, sizeof shape->id); + /* Copy parent id from breadcrumb. */ + if (p->breadcrumb_len > 0) { + memcpy(shape->id_parent, p->breadcrumb[0], sizeof shape->id_parent); + } + else { + memcpy(shape->id_parent, attr->id, sizeof shape->id_parent); + } + + scale = nsvg__getAverageScale(attr->xform); + shape->strokeWidth = attr->strokeWidth * scale; + shape->strokeDashOffset = attr->strokeDashOffset * scale; + shape->strokeDashCount = (char)attr->strokeDashCount; + for (i = 0; i < attr->strokeDashCount; i++) + shape->strokeDashArray[i] = attr->strokeDashArray[i] * scale; + shape->strokeLineJoin = attr->strokeLineJoin; + shape->strokeLineCap = attr->strokeLineCap; + shape->miterLimit = attr->miterLimit; + shape->fillRule = attr->fillRule; + shape->opacity = attr->opacity; + + shape->paths = p->plist; + p->plist = NULL; + + // Calculate shape bounds + shape->bounds[0] = shape->paths->bounds[0]; + shape->bounds[1] = shape->paths->bounds[1]; + shape->bounds[2] = shape->paths->bounds[2]; + shape->bounds[3] = shape->paths->bounds[3]; + for (path = shape->paths->next; path != NULL; path = path->next) { + shape->bounds[0] = nsvg__minf(shape->bounds[0], path->bounds[0]); + shape->bounds[1] = nsvg__minf(shape->bounds[1], path->bounds[1]); + shape->bounds[2] = nsvg__maxf(shape->bounds[2], path->bounds[2]); + shape->bounds[3] = nsvg__maxf(shape->bounds[3], path->bounds[3]); + } + + // Set fill + if (attr->hasFill == 0) { + shape->fill.type = NSVG_PAINT_NONE; + } + else if (attr->hasFill == 1) { + shape->fill.type = NSVG_PAINT_COLOR; + shape->fill.color = attr->fillColor; + shape->fill.color |= (unsigned int)(attr->fillOpacity * 255) << 24; + } + else if (attr->hasFill == 2) { + float inv[6], localBounds[4]; + nsvg__xformInverse(inv, attr->xform); + nsvg__getLocalBounds(localBounds, shape, inv); + shape->fill.gradient = nsvg__createGradient( + p, attr->fillGradient, localBounds, &shape->fill.type); + if (shape->fill.gradient == NULL) { + shape->fill.type = NSVG_PAINT_NONE; + } + } + + // Set stroke + if (attr->hasStroke == 0) { + shape->stroke.type = NSVG_PAINT_NONE; + } + else if (attr->hasStroke == 1) { + shape->stroke.type = NSVG_PAINT_COLOR; + shape->stroke.color = attr->strokeColor; + shape->stroke.color |= (unsigned int)(attr->strokeOpacity * 255) << 24; + } + else if (attr->hasStroke == 2) { + float inv[6], localBounds[4]; + nsvg__xformInverse(inv, attr->xform); + nsvg__getLocalBounds(localBounds, shape, inv); + shape->stroke.gradient = nsvg__createGradient( + p, attr->strokeGradient, localBounds, &shape->stroke.type); + if (shape->stroke.gradient == NULL) + shape->stroke.type = NSVG_PAINT_NONE; + } + + // Set flags + shape->flags = (attr->visible ? NSVG_FLAGS_VISIBLE : 0x00); + + // Add to tail + if (p->image->shapes == NULL) + p->image->shapes = shape; + else + p->shapesTail->next = shape; + p->shapesTail = shape; + + return; + +error: + if (shape) + free(shape); +} + +static void nsvg__addPath(NSVGparser *p, char closed) +{ + NSVGattrib *attr = nsvg__getAttr(p); + NSVGpath *path = NULL; + float bounds[4]; + float *curve; + int i; + + if (p->npts < 4) + return; + + if (closed) + nsvg__lineTo(p, p->pts[0], p->pts[1]); + + // Expect 1 + N*3 points (N = number of cubic bezier segments). + if ((p->npts % 3) != 1) + return; + + path = (NSVGpath *)malloc(sizeof(NSVGpath)); + if (path == NULL) + goto error; + memset(path, 0, sizeof(NSVGpath)); + + path->pts = (float *)malloc(p->npts * 2 * sizeof(float)); + if (path->pts == NULL) + goto error; + path->closed = closed; + path->npts = p->npts; + + // Transform path. + for (i = 0; i < p->npts; ++i) + nsvg__xformPoint( + &path->pts[i * 2], &path->pts[i * 2 + 1], p->pts[i * 2], p->pts[i * 2 + 1], attr->xform); + + // Find bounds + for (i = 0; i < path->npts - 1; i += 3) { + curve = &path->pts[i * 2]; + nsvg__curveBounds(bounds, curve); + if (i == 0) { + path->bounds[0] = bounds[0]; + path->bounds[1] = bounds[1]; + path->bounds[2] = bounds[2]; + path->bounds[3] = bounds[3]; + } + else { + path->bounds[0] = nsvg__minf(path->bounds[0], bounds[0]); + path->bounds[1] = nsvg__minf(path->bounds[1], bounds[1]); + path->bounds[2] = nsvg__maxf(path->bounds[2], bounds[2]); + path->bounds[3] = nsvg__maxf(path->bounds[3], bounds[3]); + } + } + + path->next = p->plist; + p->plist = path; + + return; + +error: + if (path != NULL) { + if (path->pts != NULL) + free(path->pts); + free(path); + } +} + +// We roll our own string to float because the std library one uses locale and messes things up. +static double nsvg__atof(const char *s) +{ + char *cur = (char *)s; + char *end = NULL; + double res = 0.0, sign = 1.0; + long long intPart = 0, fracPart = 0; + char hasIntPart = 0, hasFracPart = 0; + + // Parse optional sign + if (*cur == '+') { + cur++; + } + else if (*cur == '-') { + sign = -1; + cur++; + } + + // Parse integer part + if (nsvg__isdigit(*cur)) { + // Parse digit sequence + intPart = strtoll(cur, &end, 10); + if (cur != end) { + res = (double)intPart; + hasIntPart = 1; + cur = end; + } + } + + // Parse fractional part. + if (*cur == '.') { + cur++; // Skip '.' + if (nsvg__isdigit(*cur)) { + // Parse digit sequence + fracPart = strtoll(cur, &end, 10); + if (cur != end) { + res += (double)fracPart / pow(10.0, (double)(end - cur)); + hasFracPart = 1; + cur = end; + } + } + } + + // A valid number should have integer or fractional part. + if (!hasIntPart && !hasFracPart) + return 0.0; + + // Parse optional exponent + if (*cur == 'e' || *cur == 'E') { + long expPart = 0; + cur++; // skip 'E' + expPart = strtol(cur, &end, 10); // Parse digit sequence with sign + if (cur != end) { + res *= pow(10.0, (double)expPart); + } + } + + return res * sign; +} + +static const char *nsvg__parseNumber(const char *s, char *it, const int size) +{ + const int last = size - 1; + int i = 0; + + // sign + if (*s == '-' || *s == '+') { + if (i < last) + it[i++] = *s; + s++; + } + // integer part + while (*s && nsvg__isdigit(*s)) { + if (i < last) + it[i++] = *s; + s++; + } + if (*s == '.') { + // decimal point + if (i < last) + it[i++] = *s; + s++; + // fraction part + while (*s && nsvg__isdigit(*s)) { + if (i < last) + it[i++] = *s; + s++; + } + } + // exponent + if ((*s == 'e' || *s == 'E') && (s[1] != 'm' && s[1] != 'x')) { + if (i < last) + it[i++] = *s; + s++; + if (*s == '-' || *s == '+') { + if (i < last) + it[i++] = *s; + s++; + } + while (*s && nsvg__isdigit(*s)) { + if (i < last) + it[i++] = *s; + s++; + } + } + it[i] = '\0'; + + return s; +} + +static const char *nsvg__getNextPathItem(const char *s, char *it) +{ + it[0] = '\0'; + // Skip white spaces and commas + while (*s && (nsvg__isspace(*s) || *s == ',')) + s++; + if (!*s) + return s; + if (*s == '-' || *s == '+' || *s == '.' || nsvg__isdigit(*s)) { + s = nsvg__parseNumber(s, it, 64); + } + else { + // Parse command + it[0] = *s++; + it[1] = '\0'; + return s; + } + + return s; +} + +static unsigned int nsvg__parseColorHex(const char *str) +{ + unsigned int c = 0, r = 0, g = 0, b = 0; + int n = 0; + str++; // skip # + // Calculate number of characters. + while (str[n] && !nsvg__isspace(str[n])) + n++; + if (n == 6) { + sscanf(str, "%x", &c); + } + else if (n == 3) { + sscanf(str, "%x", &c); + c = (c & 0xf) | ((c & 0xf0) << 4) | ((c & 0xf00) << 8); + c |= c << 4; + } + r = (c >> 16) & 0xff; + g = (c >> 8) & 0xff; + b = c & 0xff; + return NSVG_RGB(r, g, b); +} + +static unsigned int nsvg__parseColorRGB(const char *str) +{ + int r = -1, g = -1, b = -1; + char s1[32] = "", s2[32] = ""; + sscanf(str + 4, "%d%[%%, \t]%d%[%%, \t]%d", &r, s1, &g, s2, &b); + if (strchr(s1, '%')) { + return NSVG_RGB((r * 255) / 100, (g * 255) / 100, (b * 255) / 100); + } + else { + return NSVG_RGB(r, g, b); + } +} + +typedef struct NSVGNamedColor { + const char *name; + unsigned int color; +} NSVGNamedColor; + +NSVGNamedColor nsvg__colors[] = { + + {"red", NSVG_RGB(255, 0, 0)}, + {"green", NSVG_RGB(0, 128, 0)}, + {"blue", NSVG_RGB(0, 0, 255)}, + {"yellow", NSVG_RGB(255, 255, 0)}, + {"cyan", NSVG_RGB(0, 255, 255)}, + {"magenta", NSVG_RGB(255, 0, 255)}, + {"black", NSVG_RGB(0, 0, 0)}, + {"grey", NSVG_RGB(128, 128, 128)}, + {"gray", NSVG_RGB(128, 128, 128)}, + {"white", NSVG_RGB(255, 255, 255)}, + +#ifdef NANOSVG_ALL_COLOR_KEYWORDS + {"aliceblue", NSVG_RGB(240, 248, 255)}, + {"antiquewhite", NSVG_RGB(250, 235, 215)}, + {"aqua", NSVG_RGB(0, 255, 255)}, + {"aquamarine", NSVG_RGB(127, 255, 212)}, + {"azure", NSVG_RGB(240, 255, 255)}, + {"beige", NSVG_RGB(245, 245, 220)}, + {"bisque", NSVG_RGB(255, 228, 196)}, + {"blanchedalmond", NSVG_RGB(255, 235, 205)}, + {"blueviolet", NSVG_RGB(138, 43, 226)}, + {"brown", NSVG_RGB(165, 42, 42)}, + {"burlywood", NSVG_RGB(222, 184, 135)}, + {"cadetblue", NSVG_RGB(95, 158, 160)}, + {"chartreuse", NSVG_RGB(127, 255, 0)}, + {"chocolate", NSVG_RGB(210, 105, 30)}, + {"coral", NSVG_RGB(255, 127, 80)}, + {"cornflowerblue", NSVG_RGB(100, 149, 237)}, + {"cornsilk", NSVG_RGB(255, 248, 220)}, + {"crimson", NSVG_RGB(220, 20, 60)}, + {"darkblue", NSVG_RGB(0, 0, 139)}, + {"darkcyan", NSVG_RGB(0, 139, 139)}, + {"darkgoldenrod", NSVG_RGB(184, 134, 11)}, + {"darkgray", NSVG_RGB(169, 169, 169)}, + {"darkgreen", NSVG_RGB(0, 100, 0)}, + {"darkgrey", NSVG_RGB(169, 169, 169)}, + {"darkkhaki", NSVG_RGB(189, 183, 107)}, + {"darkmagenta", NSVG_RGB(139, 0, 139)}, + {"darkolivegreen", NSVG_RGB(85, 107, 47)}, + {"darkorange", NSVG_RGB(255, 140, 0)}, + {"darkorchid", NSVG_RGB(153, 50, 204)}, + {"darkred", NSVG_RGB(139, 0, 0)}, + {"darksalmon", NSVG_RGB(233, 150, 122)}, + {"darkseagreen", NSVG_RGB(143, 188, 143)}, + {"darkslateblue", NSVG_RGB(72, 61, 139)}, + {"darkslategray", NSVG_RGB(47, 79, 79)}, + {"darkslategrey", NSVG_RGB(47, 79, 79)}, + {"darkturquoise", NSVG_RGB(0, 206, 209)}, + {"darkviolet", NSVG_RGB(148, 0, 211)}, + {"deeppink", NSVG_RGB(255, 20, 147)}, + {"deepskyblue", NSVG_RGB(0, 191, 255)}, + {"dimgray", NSVG_RGB(105, 105, 105)}, + {"dimgrey", NSVG_RGB(105, 105, 105)}, + {"dodgerblue", NSVG_RGB(30, 144, 255)}, + {"firebrick", NSVG_RGB(178, 34, 34)}, + {"floralwhite", NSVG_RGB(255, 250, 240)}, + {"forestgreen", NSVG_RGB(34, 139, 34)}, + {"fuchsia", NSVG_RGB(255, 0, 255)}, + {"gainsboro", NSVG_RGB(220, 220, 220)}, + {"ghostwhite", NSVG_RGB(248, 248, 255)}, + {"gold", NSVG_RGB(255, 215, 0)}, + {"goldenrod", NSVG_RGB(218, 165, 32)}, + {"greenyellow", NSVG_RGB(173, 255, 47)}, + {"honeydew", NSVG_RGB(240, 255, 240)}, + {"hotpink", NSVG_RGB(255, 105, 180)}, + {"indianred", NSVG_RGB(205, 92, 92)}, + {"indigo", NSVG_RGB(75, 0, 130)}, + {"ivory", NSVG_RGB(255, 255, 240)}, + {"khaki", NSVG_RGB(240, 230, 140)}, + {"lavender", NSVG_RGB(230, 230, 250)}, + {"lavenderblush", NSVG_RGB(255, 240, 245)}, + {"lawngreen", NSVG_RGB(124, 252, 0)}, + {"lemonchiffon", NSVG_RGB(255, 250, 205)}, + {"lightblue", NSVG_RGB(173, 216, 230)}, + {"lightcoral", NSVG_RGB(240, 128, 128)}, + {"lightcyan", NSVG_RGB(224, 255, 255)}, + {"lightgoldenrodyellow", NSVG_RGB(250, 250, 210)}, + {"lightgray", NSVG_RGB(211, 211, 211)}, + {"lightgreen", NSVG_RGB(144, 238, 144)}, + {"lightgrey", NSVG_RGB(211, 211, 211)}, + {"lightpink", NSVG_RGB(255, 182, 193)}, + {"lightsalmon", NSVG_RGB(255, 160, 122)}, + {"lightseagreen", NSVG_RGB(32, 178, 170)}, + {"lightskyblue", NSVG_RGB(135, 206, 250)}, + {"lightslategray", NSVG_RGB(119, 136, 153)}, + {"lightslategrey", NSVG_RGB(119, 136, 153)}, + {"lightsteelblue", NSVG_RGB(176, 196, 222)}, + {"lightyellow", NSVG_RGB(255, 255, 224)}, + {"lime", NSVG_RGB(0, 255, 0)}, + {"limegreen", NSVG_RGB(50, 205, 50)}, + {"linen", NSVG_RGB(250, 240, 230)}, + {"maroon", NSVG_RGB(128, 0, 0)}, + {"mediumaquamarine", NSVG_RGB(102, 205, 170)}, + {"mediumblue", NSVG_RGB(0, 0, 205)}, + {"mediumorchid", NSVG_RGB(186, 85, 211)}, + {"mediumpurple", NSVG_RGB(147, 112, 219)}, + {"mediumseagreen", NSVG_RGB(60, 179, 113)}, + {"mediumslateblue", NSVG_RGB(123, 104, 238)}, + {"mediumspringgreen", NSVG_RGB(0, 250, 154)}, + {"mediumturquoise", NSVG_RGB(72, 209, 204)}, + {"mediumvioletred", NSVG_RGB(199, 21, 133)}, + {"midnightblue", NSVG_RGB(25, 25, 112)}, + {"mintcream", NSVG_RGB(245, 255, 250)}, + {"mistyrose", NSVG_RGB(255, 228, 225)}, + {"moccasin", NSVG_RGB(255, 228, 181)}, + {"navajowhite", NSVG_RGB(255, 222, 173)}, + {"navy", NSVG_RGB(0, 0, 128)}, + {"oldlace", NSVG_RGB(253, 245, 230)}, + {"olive", NSVG_RGB(128, 128, 0)}, + {"olivedrab", NSVG_RGB(107, 142, 35)}, + {"orange", NSVG_RGB(255, 165, 0)}, + {"orangered", NSVG_RGB(255, 69, 0)}, + {"orchid", NSVG_RGB(218, 112, 214)}, + {"palegoldenrod", NSVG_RGB(238, 232, 170)}, + {"palegreen", NSVG_RGB(152, 251, 152)}, + {"paleturquoise", NSVG_RGB(175, 238, 238)}, + {"palevioletred", NSVG_RGB(219, 112, 147)}, + {"papayawhip", NSVG_RGB(255, 239, 213)}, + {"peachpuff", NSVG_RGB(255, 218, 185)}, + {"peru", NSVG_RGB(205, 133, 63)}, + {"pink", NSVG_RGB(255, 192, 203)}, + {"plum", NSVG_RGB(221, 160, 221)}, + {"powderblue", NSVG_RGB(176, 224, 230)}, + {"purple", NSVG_RGB(128, 0, 128)}, + {"rosybrown", NSVG_RGB(188, 143, 143)}, + {"royalblue", NSVG_RGB(65, 105, 225)}, + {"saddlebrown", NSVG_RGB(139, 69, 19)}, + {"salmon", NSVG_RGB(250, 128, 114)}, + {"sandybrown", NSVG_RGB(244, 164, 96)}, + {"seagreen", NSVG_RGB(46, 139, 87)}, + {"seashell", NSVG_RGB(255, 245, 238)}, + {"sienna", NSVG_RGB(160, 82, 45)}, + {"silver", NSVG_RGB(192, 192, 192)}, + {"skyblue", NSVG_RGB(135, 206, 235)}, + {"slateblue", NSVG_RGB(106, 90, 205)}, + {"slategray", NSVG_RGB(112, 128, 144)}, + {"slategrey", NSVG_RGB(112, 128, 144)}, + {"snow", NSVG_RGB(255, 250, 250)}, + {"springgreen", NSVG_RGB(0, 255, 127)}, + {"steelblue", NSVG_RGB(70, 130, 180)}, + {"tan", NSVG_RGB(210, 180, 140)}, + {"teal", NSVG_RGB(0, 128, 128)}, + {"thistle", NSVG_RGB(216, 191, 216)}, + {"tomato", NSVG_RGB(255, 99, 71)}, + {"turquoise", NSVG_RGB(64, 224, 208)}, + {"violet", NSVG_RGB(238, 130, 238)}, + {"wheat", NSVG_RGB(245, 222, 179)}, + {"whitesmoke", NSVG_RGB(245, 245, 245)}, + {"yellowgreen", NSVG_RGB(154, 205, 50)}, +#endif +}; + +static unsigned int nsvg__parseColorName(const char *str) +{ + int i, ncolors = sizeof(nsvg__colors) / sizeof(NSVGNamedColor); + + for (i = 0; i < ncolors; i++) { + if (strcmp(nsvg__colors[i].name, str) == 0) { + return nsvg__colors[i].color; + } + } + + return NSVG_RGB(128, 128, 128); +} + +static unsigned int nsvg__parseColor(const char *str) +{ + size_t len = 0; + while (*str == ' ') + ++str; + len = strlen(str); + if (len >= 1 && *str == '#') + return nsvg__parseColorHex(str); + else if (len >= 4 && str[0] == 'r' && str[1] == 'g' && str[2] == 'b' && str[3] == '(') + return nsvg__parseColorRGB(str); + return nsvg__parseColorName(str); +} + +static float nsvg__parseOpacity(const char *str) +{ + float val = nsvg__atof(str); + if (val < 0.0f) + val = 0.0f; + if (val > 1.0f) + val = 1.0f; + return val; +} + +static float nsvg__parseMiterLimit(const char *str) +{ + float val = nsvg__atof(str); + if (val < 0.0f) + val = 0.0f; + return val; +} + +static int nsvg__parseUnits(const char *units) +{ + if (units[0] == 'p' && units[1] == 'x') + return NSVG_UNITS_PX; + else if (units[0] == 'p' && units[1] == 't') + return NSVG_UNITS_PT; + else if (units[0] == 'p' && units[1] == 'c') + return NSVG_UNITS_PC; + else if (units[0] == 'm' && units[1] == 'm') + return NSVG_UNITS_MM; + else if (units[0] == 'c' && units[1] == 'm') + return NSVG_UNITS_CM; + else if (units[0] == 'i' && units[1] == 'n') + return NSVG_UNITS_IN; + else if (units[0] == '%') + return NSVG_UNITS_PERCENT; + else if (units[0] == 'e' && units[1] == 'm') + return NSVG_UNITS_EM; + else if (units[0] == 'e' && units[1] == 'x') + return NSVG_UNITS_EX; + return NSVG_UNITS_USER; +} + +static int nsvg__isCoordinate(const char *s) +{ + // optional sign + if (*s == '-' || *s == '+') + s++; + // must have at least one digit + return nsvg__isdigit(*s); +} + +static NSVGcoordinate nsvg__parseCoordinateRaw(const char *str) +{ + NSVGcoordinate coord = {0, NSVG_UNITS_USER}; + char buf[64]; + coord.units = nsvg__parseUnits(nsvg__parseNumber(str, buf, 64)); + coord.value = nsvg__atof(buf); + return coord; +} + +static NSVGcoordinate nsvg__coord(float v, int units) +{ + NSVGcoordinate coord = {v, units}; + return coord; +} + +static float nsvg__parseCoordinate(NSVGparser *p, const char *str, float orig, float length) +{ + NSVGcoordinate coord = nsvg__parseCoordinateRaw(str); + return nsvg__convertToPixels(p, coord, orig, length); +} + +static int nsvg__parseTransformArgs(const char *str, float *args, int maxNa, int *na) +{ + const char *end; + const char *ptr; + char it[64]; + + *na = 0; + ptr = str; + while (*ptr && *ptr != '(') + ++ptr; + if (*ptr == 0) + return 1; + end = ptr; + while (*end && *end != ')') + ++end; + if (*end == 0) + return 1; + + while (ptr < end) { + if (*ptr == '-' || *ptr == '+' || *ptr == '.' || nsvg__isdigit(*ptr)) { + if (*na >= maxNa) + return 0; + ptr = nsvg__parseNumber(ptr, it, 64); + args[(*na)++] = (float)nsvg__atof(it); + } + else { + ++ptr; + } + } + return (int)(end - str); +} + +static int nsvg__parseMatrix(float *xform, const char *str) +{ + float t[6]; + int na = 0; + int len = nsvg__parseTransformArgs(str, t, 6, &na); + if (na != 6) + return len; + memcpy(xform, t, sizeof(float) * 6); + return len; +} + +static int nsvg__parseTranslate(float *xform, const char *str) +{ + float args[2]; + float t[6]; + int na = 0; + int len = nsvg__parseTransformArgs(str, args, 2, &na); + if (na == 1) + args[1] = 0.0; + + nsvg__xformSetTranslation(t, args[0], args[1]); + memcpy(xform, t, sizeof(float) * 6); + return len; +} + +static int nsvg__parseScale(float *xform, const char *str) +{ + float args[2]; + int na = 0; + float t[6]; + int len = nsvg__parseTransformArgs(str, args, 2, &na); + if (na == 1) + args[1] = args[0]; + nsvg__xformSetScale(t, args[0], args[1]); + memcpy(xform, t, sizeof(float) * 6); + return len; +} + +static int nsvg__parseSkewX(float *xform, const char *str) +{ + float args[1]; + int na = 0; + float t[6]; + int len = nsvg__parseTransformArgs(str, args, 1, &na); + nsvg__xformSetSkewX(t, args[0] / 180.0f * NSVG_PI); + memcpy(xform, t, sizeof(float) * 6); + return len; +} + +static int nsvg__parseSkewY(float *xform, const char *str) +{ + float args[1]; + int na = 0; + float t[6]; + int len = nsvg__parseTransformArgs(str, args, 1, &na); + nsvg__xformSetSkewY(t, args[0] / 180.0f * NSVG_PI); + memcpy(xform, t, sizeof(float) * 6); + return len; +} + +static int nsvg__parseRotate(float *xform, const char *str) +{ + float args[3]; + int na = 0; + float m[6]; + float t[6]; + int len = nsvg__parseTransformArgs(str, args, 3, &na); + if (na == 1) + args[1] = args[2] = 0.0f; + nsvg__xformIdentity(m); + + if (na > 1) { + nsvg__xformSetTranslation(t, -args[1], -args[2]); + nsvg__xformMultiply(m, t); + } + + nsvg__xformSetRotation(t, args[0] / 180.0f * NSVG_PI); + nsvg__xformMultiply(m, t); + + if (na > 1) { + nsvg__xformSetTranslation(t, args[1], args[2]); + nsvg__xformMultiply(m, t); + } + + memcpy(xform, m, sizeof(float) * 6); + + return len; +} + +static void nsvg__parseTransform(float *xform, const char *str) +{ + float t[6]; + int len; + nsvg__xformIdentity(xform); + while (*str) { + if (strncmp(str, "matrix", 6) == 0) + len = nsvg__parseMatrix(t, str); + else if (strncmp(str, "translate", 9) == 0) + len = nsvg__parseTranslate(t, str); + else if (strncmp(str, "scale", 5) == 0) + len = nsvg__parseScale(t, str); + else if (strncmp(str, "rotate", 6) == 0) + len = nsvg__parseRotate(t, str); + else if (strncmp(str, "skewX", 5) == 0) + len = nsvg__parseSkewX(t, str); + else if (strncmp(str, "skewY", 5) == 0) + len = nsvg__parseSkewY(t, str); + else { + ++str; + continue; + } + if (len != 0) { + str += len; + } + else { + ++str; + continue; + } + + nsvg__xformPremultiply(xform, t); + } +} + +static void nsvg__parseUrl(char *id, const char *str) +{ + int i = 0; + str += 4; // "url("; + if (*str == '#') + str++; + while (i < 63 && *str != ')') { + id[i] = *str++; + i++; + } + id[i] = '\0'; +} + +static char nsvg__parseLineCap(const char *str) +{ + if (strcmp(str, "butt") == 0) + return NSVG_CAP_BUTT; + else if (strcmp(str, "round") == 0) + return NSVG_CAP_ROUND; + else if (strcmp(str, "square") == 0) + return NSVG_CAP_SQUARE; + // TODO: handle inherit. + return NSVG_CAP_BUTT; +} + +static char nsvg__parseLineJoin(const char *str) +{ + if (strcmp(str, "miter") == 0) + return NSVG_JOIN_MITER; + else if (strcmp(str, "round") == 0) + return NSVG_JOIN_ROUND; + else if (strcmp(str, "bevel") == 0) + return NSVG_JOIN_BEVEL; + // TODO: handle inherit. + return NSVG_JOIN_MITER; +} + +static char nsvg__parseFillRule(const char *str) +{ + if (strcmp(str, "nonzero") == 0) + return NSVG_FILLRULE_NONZERO; + else if (strcmp(str, "evenodd") == 0) + return NSVG_FILLRULE_EVENODD; + // TODO: handle inherit. + return NSVG_FILLRULE_NONZERO; +} + +static const char *nsvg__getNextDashItem(const char *s, char *it) +{ + int n = 0; + it[0] = '\0'; + // Skip white spaces and commas + while (*s && (nsvg__isspace(*s) || *s == ',')) + s++; + // Advance until whitespace, comma or end. + while (*s && (!nsvg__isspace(*s) && *s != ',')) { + if (n < 63) + it[n++] = *s; + s++; + } + it[n++] = '\0'; + return s; +} + +static int nsvg__parseStrokeDashArray(NSVGparser *p, const char *str, float *strokeDashArray) +{ + char item[64]; + int count = 0, i; + float sum = 0.0f; + + // Handle "none" + if (str[0] == 'n') + return 0; + + // Parse dashes + while (*str) { + str = nsvg__getNextDashItem(str, item); + if (!*item) + break; + if (count < NSVG_MAX_DASHES) + strokeDashArray[count++] = fabsf( + nsvg__parseCoordinate(p, item, 0.0f, nsvg__actualLength(p))); + } + + for (i = 0; i < count; i++) + sum += strokeDashArray[i]; + if (sum <= 1e-6f) + count = 0; + + return count; +} + +static void nsvg__parseStyle(NSVGparser *p, const char *str); + +static int nsvg__parseAttr(NSVGparser *p, const char *name, const char *value) +{ + float xform[6]; + NSVGattrib *attr = nsvg__getAttr(p); + if (!attr) + return 0; + + if (strcmp(name, "style") == 0) { + nsvg__parseStyle(p, value); + } + else if (strcmp(name, "display") == 0) { + if (strcmp(value, "none") == 0) + attr->visible = 0; + // Don't reset ->visible on display:inline, one display:none hides the whole subtree + } + else if (strcmp(name, "fill") == 0) { + if (strcmp(value, "none") == 0) { + attr->hasFill = 0; + } + else if (strncmp(value, "url(", 4) == 0) { + attr->hasFill = 2; + nsvg__parseUrl(attr->fillGradient, value); + } + else { + attr->hasFill = 1; + attr->fillColor = nsvg__parseColor(value); + } + } + else if (strcmp(name, "opacity") == 0) { + attr->opacity = nsvg__parseOpacity(value); + } + else if (strcmp(name, "fill-opacity") == 0) { + attr->fillOpacity = nsvg__parseOpacity(value); + } + else if (strcmp(name, "stroke") == 0) { + if (strcmp(value, "none") == 0) { + attr->hasStroke = 0; + } + else if (strncmp(value, "url(", 4) == 0) { + attr->hasStroke = 2; + nsvg__parseUrl(attr->strokeGradient, value); + } + else { + attr->hasStroke = 1; + attr->strokeColor = nsvg__parseColor(value); + } + } + else if (strcmp(name, "stroke-width") == 0) { + attr->strokeWidth = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); + } + else if (strcmp(name, "stroke-dasharray") == 0) { + attr->strokeDashCount = nsvg__parseStrokeDashArray(p, value, attr->strokeDashArray); + } + else if (strcmp(name, "stroke-dashoffset") == 0) { + attr->strokeDashOffset = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); + } + else if (strcmp(name, "stroke-opacity") == 0) { + attr->strokeOpacity = nsvg__parseOpacity(value); + } + else if (strcmp(name, "stroke-linecap") == 0) { + attr->strokeLineCap = nsvg__parseLineCap(value); + } + else if (strcmp(name, "stroke-linejoin") == 0) { + attr->strokeLineJoin = nsvg__parseLineJoin(value); + } + else if (strcmp(name, "stroke-miterlimit") == 0) { + attr->miterLimit = nsvg__parseMiterLimit(value); + } + else if (strcmp(name, "fill-rule") == 0) { + attr->fillRule = nsvg__parseFillRule(value); + } + else if (strcmp(name, "font-size") == 0) { + attr->fontSize = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); + } + else if (strcmp(name, "transform") == 0) { + nsvg__parseTransform(xform, value); + nsvg__xformPremultiply(attr->xform, xform); + } + else if (strcmp(name, "stop-color") == 0) { + attr->stopColor = nsvg__parseColor(value); + } + else if (strcmp(name, "stop-opacity") == 0) { + attr->stopOpacity = nsvg__parseOpacity(value); + } + else if (strcmp(name, "offset") == 0) { + attr->stopOffset = nsvg__parseCoordinate(p, value, 0.0f, 1.0f); + } + else if (strcmp(name, "id") == 0) { + strncpy(attr->id, value, 63); + attr->id[63] = '\0'; + } + else { + return 0; + } + return 1; +} + +static int nsvg__parseNameValue(NSVGparser *p, const char *start, const char *end) +{ + const char *str; + const char *val; + char name[512]; + char value[512]; + int n; + + str = start; + while (str < end && *str != ':') + ++str; + + val = str; + + // Right Trim + while (str > start && (*str == ':' || nsvg__isspace(*str))) + --str; + ++str; + + n = (int)(str - start); + if (n > 511) + n = 511; + if (n) + memcpy(name, start, n); + name[n] = 0; + + while (val < end && (*val == ':' || nsvg__isspace(*val))) + ++val; + + n = (int)(end - val); + if (n > 511) + n = 511; + if (n) + memcpy(value, val, n); + value[n] = 0; + + return nsvg__parseAttr(p, name, value); +} + +static void nsvg__parseStyle(NSVGparser *p, const char *str) +{ + const char *start; + const char *end; + + while (*str) { + // Left Trim + while (*str && nsvg__isspace(*str)) + ++str; + start = str; + while (*str && *str != ';') + ++str; + end = str; + + // Right Trim + while (end > start && (*end == ';' || nsvg__isspace(*end))) + --end; + ++end; + + nsvg__parseNameValue(p, start, end); + if (*str) + ++str; + } +} + +static void nsvg__parseAttribs(NSVGparser *p, const char **attr) +{ + int i; + for (i = 0; attr[i]; i += 2) { + if (strcmp(attr[i], "style") == 0) + nsvg__parseStyle(p, attr[i + 1]); + else + nsvg__parseAttr(p, attr[i], attr[i + 1]); + } +} + +static int nsvg__getArgsPerElement(char cmd) +{ + switch (cmd) { + case 'v': + case 'V': + case 'h': + case 'H': + return 1; + case 'm': + case 'M': + case 'l': + case 'L': + case 't': + case 'T': + return 2; + case 'q': + case 'Q': + case 's': + case 'S': + return 4; + case 'c': + case 'C': + return 6; + case 'a': + case 'A': + return 7; + case 'z': + case 'Z': + return 0; + } + return -1; +} + +static void nsvg__pathMoveTo(NSVGparser *p, float *cpx, float *cpy, float *args, int rel) +{ + if (rel) { + *cpx += args[0]; + *cpy += args[1]; + } + else { + *cpx = args[0]; + *cpy = args[1]; + } + nsvg__moveTo(p, *cpx, *cpy); +} + +static void nsvg__pathLineTo(NSVGparser *p, float *cpx, float *cpy, float *args, int rel) +{ + if (rel) { + *cpx += args[0]; + *cpy += args[1]; + } + else { + *cpx = args[0]; + *cpy = args[1]; + } + nsvg__lineTo(p, *cpx, *cpy); +} + +static void nsvg__pathHLineTo(NSVGparser *p, float *cpx, float *cpy, float *args, int rel) +{ + if (rel) + *cpx += args[0]; + else + *cpx = args[0]; + nsvg__lineTo(p, *cpx, *cpy); +} + +static void nsvg__pathVLineTo(NSVGparser *p, float *cpx, float *cpy, float *args, int rel) +{ + if (rel) + *cpy += args[0]; + else + *cpy = args[0]; + nsvg__lineTo(p, *cpx, *cpy); +} + +static void nsvg__pathCubicBezTo( + NSVGparser *p, float *cpx, float *cpy, float *cpx2, float *cpy2, float *args, int rel) +{ + float x2, y2, cx1, cy1, cx2, cy2; + + if (rel) { + cx1 = *cpx + args[0]; + cy1 = *cpy + args[1]; + cx2 = *cpx + args[2]; + cy2 = *cpy + args[3]; + x2 = *cpx + args[4]; + y2 = *cpy + args[5]; + } + else { + cx1 = args[0]; + cy1 = args[1]; + cx2 = args[2]; + cy2 = args[3]; + x2 = args[4]; + y2 = args[5]; + } + + nsvg__cubicBezTo(p, cx1, cy1, cx2, cy2, x2, y2); + + *cpx2 = cx2; + *cpy2 = cy2; + *cpx = x2; + *cpy = y2; +} + +static void nsvg__pathCubicBezShortTo( + NSVGparser *p, float *cpx, float *cpy, float *cpx2, float *cpy2, float *args, int rel) +{ + float x1, y1, x2, y2, cx1, cy1, cx2, cy2; + + x1 = *cpx; + y1 = *cpy; + if (rel) { + cx2 = *cpx + args[0]; + cy2 = *cpy + args[1]; + x2 = *cpx + args[2]; + y2 = *cpy + args[3]; + } + else { + cx2 = args[0]; + cy2 = args[1]; + x2 = args[2]; + y2 = args[3]; + } + + cx1 = 2 * x1 - *cpx2; + cy1 = 2 * y1 - *cpy2; + + nsvg__cubicBezTo(p, cx1, cy1, cx2, cy2, x2, y2); + + *cpx2 = cx2; + *cpy2 = cy2; + *cpx = x2; + *cpy = y2; +} + +static void nsvg__pathQuadBezTo( + NSVGparser *p, float *cpx, float *cpy, float *cpx2, float *cpy2, float *args, int rel) +{ + float x1, y1, x2, y2, cx, cy; + float cx1, cy1, cx2, cy2; + + x1 = *cpx; + y1 = *cpy; + if (rel) { + cx = *cpx + args[0]; + cy = *cpy + args[1]; + x2 = *cpx + args[2]; + y2 = *cpy + args[3]; + } + else { + cx = args[0]; + cy = args[1]; + x2 = args[2]; + y2 = args[3]; + } + + // Convert to cubic bezier + cx1 = x1 + 2.0f / 3.0f * (cx - x1); + cy1 = y1 + 2.0f / 3.0f * (cy - y1); + cx2 = x2 + 2.0f / 3.0f * (cx - x2); + cy2 = y2 + 2.0f / 3.0f * (cy - y2); + + nsvg__cubicBezTo(p, cx1, cy1, cx2, cy2, x2, y2); + + *cpx2 = cx; + *cpy2 = cy; + *cpx = x2; + *cpy = y2; +} + +static void nsvg__pathQuadBezShortTo( + NSVGparser *p, float *cpx, float *cpy, float *cpx2, float *cpy2, float *args, int rel) +{ + float x1, y1, x2, y2, cx, cy; + float cx1, cy1, cx2, cy2; + + x1 = *cpx; + y1 = *cpy; + if (rel) { + x2 = *cpx + args[0]; + y2 = *cpy + args[1]; + } + else { + x2 = args[0]; + y2 = args[1]; + } + + cx = 2 * x1 - *cpx2; + cy = 2 * y1 - *cpy2; + + // Convert to cubix bezier + cx1 = x1 + 2.0f / 3.0f * (cx - x1); + cy1 = y1 + 2.0f / 3.0f * (cy - y1); + cx2 = x2 + 2.0f / 3.0f * (cx - x2); + cy2 = y2 + 2.0f / 3.0f * (cy - y2); + + nsvg__cubicBezTo(p, cx1, cy1, cx2, cy2, x2, y2); + + *cpx2 = cx; + *cpy2 = cy; + *cpx = x2; + *cpy = y2; +} + +static float nsvg__sqr(float x) +{ + return x * x; +} +static float nsvg__vmag(float x, float y) +{ + return sqrtf(x * x + y * y); +} + +static float nsvg__vecrat(float ux, float uy, float vx, float vy) +{ + return (ux * vx + uy * vy) / (nsvg__vmag(ux, uy) * nsvg__vmag(vx, vy)); +} + +static float nsvg__vecang(float ux, float uy, float vx, float vy) +{ + float r = nsvg__vecrat(ux, uy, vx, vy); + if (r < -1.0f) + r = -1.0f; + if (r > 1.0f) + r = 1.0f; + return ((ux * vy < uy * vx) ? -1.0f : 1.0f) * acosf(r); +} + +static void nsvg__pathArcTo(NSVGparser *p, float *cpx, float *cpy, float *args, int rel) +{ + // Ported from canvg (https://code.google.com/p/canvg/) + float rx, ry, rotx; + float x1, y1, x2, y2, cx, cy, dx, dy, d; + float x1p, y1p, cxp, cyp, s, sa, sb; + float ux, uy, vx, vy, a1, da; + float x, y, tanx, tany, a, px = 0, py = 0, ptanx = 0, ptany = 0, t[6]; + float sinrx, cosrx; + int fa, fs; + int i, ndivs; + float hda, kappa; + + rx = fabsf(args[0]); // y radius + ry = fabsf(args[1]); // x radius + rotx = args[2] / 180.0f * NSVG_PI; // x rotation angle + fa = fabsf(args[3]) > 1e-6 ? 1 : 0; // Large arc + fs = fabsf(args[4]) > 1e-6 ? 1 : 0; // Sweep direction + x1 = *cpx; // start point + y1 = *cpy; + if (rel) { // end point + x2 = *cpx + args[5]; + y2 = *cpy + args[6]; + } + else { + x2 = args[5]; + y2 = args[6]; + } + + dx = x1 - x2; + dy = y1 - y2; + d = sqrtf(dx * dx + dy * dy); + if (d < 1e-6f || rx < 1e-6f || ry < 1e-6f) { + // The arc degenerates to a line + nsvg__lineTo(p, x2, y2); + *cpx = x2; + *cpy = y2; + return; + } + + sinrx = sinf(rotx); + cosrx = cosf(rotx); + + // Convert to center point parameterization. + // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes + // 1) Compute x1', y1' + x1p = cosrx * dx / 2.0f + sinrx * dy / 2.0f; + y1p = -sinrx * dx / 2.0f + cosrx * dy / 2.0f; + d = nsvg__sqr(x1p) / nsvg__sqr(rx) + nsvg__sqr(y1p) / nsvg__sqr(ry); + if (d > 1) { + d = sqrtf(d); + rx *= d; + ry *= d; + } + // 2) Compute cx', cy' + s = 0.0f; + sa = nsvg__sqr(rx) * nsvg__sqr(ry) - nsvg__sqr(rx) * nsvg__sqr(y1p) - + nsvg__sqr(ry) * nsvg__sqr(x1p); + sb = nsvg__sqr(rx) * nsvg__sqr(y1p) + nsvg__sqr(ry) * nsvg__sqr(x1p); + if (sa < 0.0f) + sa = 0.0f; + if (sb > 0.0f) + s = sqrtf(sa / sb); + if (fa == fs) + s = -s; + cxp = s * rx * y1p / ry; + cyp = s * -ry * x1p / rx; + + // 3) Compute cx,cy from cx',cy' + cx = (x1 + x2) / 2.0f + cosrx * cxp - sinrx * cyp; + cy = (y1 + y2) / 2.0f + sinrx * cxp + cosrx * cyp; + + // 4) Calculate theta1, and delta theta. + ux = (x1p - cxp) / rx; + uy = (y1p - cyp) / ry; + vx = (-x1p - cxp) / rx; + vy = (-y1p - cyp) / ry; + a1 = nsvg__vecang(1.0f, 0.0f, ux, uy); // Initial angle + da = nsvg__vecang(ux, uy, vx, vy); // Delta angle + + // if (vecrat(ux,uy,vx,vy) <= -1.0f) da = NSVG_PI; + // if (vecrat(ux,uy,vx,vy) >= 1.0f) da = 0; + + if (fs == 0 && da > 0) + da -= 2 * NSVG_PI; + else if (fs == 1 && da < 0) + da += 2 * NSVG_PI; + + // Approximate the arc using cubic spline segments. + t[0] = cosrx; + t[1] = sinrx; + t[2] = -sinrx; + t[3] = cosrx; + t[4] = cx; + t[5] = cy; + + // Split arc into max 90 degree segments. + // The loop assumes an iteration per end point (including start and end), this +1. + ndivs = (int)(fabsf(da) / (NSVG_PI * 0.5f) + 1.0f); + hda = (da / (float)ndivs) / 2.0f; + kappa = fabsf(4.0f / 3.0f * (1.0f - cosf(hda)) / sinf(hda)); + if (da < 0.0f) + kappa = -kappa; + + for (i = 0; i <= ndivs; i++) { + a = a1 + da * ((float)i / (float)ndivs); + dx = cosf(a); + dy = sinf(a); + nsvg__xformPoint(&x, &y, dx * rx, dy * ry, t); // position + nsvg__xformVec(&tanx, &tany, -dy * rx * kappa, dx * ry * kappa, t); // tangent + if (i > 0) + nsvg__cubicBezTo(p, px + ptanx, py + ptany, x - tanx, y - tany, x, y); + px = x; + py = y; + ptanx = tanx; + ptany = tany; + } + + *cpx = x2; + *cpy = y2; +} + +static void nsvg__parsePath(NSVGparser *p, const char **attr) +{ + const char *s = NULL; + char cmd = '\0'; + float args[10]; + int nargs; + int rargs = 0; + char initPoint; + float cpx, cpy, cpx2, cpy2; + const char *tmp[4]; + char closedFlag; + int i; + char item[64]; + + for (i = 0; attr[i]; i += 2) { + if (strcmp(attr[i], "d") == 0) { + s = attr[i + 1]; + } + else { + tmp[0] = attr[i]; + tmp[1] = attr[i + 1]; + tmp[2] = 0; + tmp[3] = 0; + nsvg__parseAttribs(p, tmp); + } + } + + if (s) { + nsvg__resetPath(p); + cpx = 0; + cpy = 0; + cpx2 = 0; + cpy2 = 0; + initPoint = 0; + closedFlag = 0; + nargs = 0; + + while (*s) { + s = nsvg__getNextPathItem(s, item); + if (!*item) + break; + if (cmd != '\0' && nsvg__isCoordinate(item)) { + if (nargs < 10) + args[nargs++] = (float)nsvg__atof(item); + if (nargs >= rargs) { + switch (cmd) { + case 'm': + case 'M': + nsvg__pathMoveTo(p, &cpx, &cpy, args, cmd == 'm' ? 1 : 0); + // Moveto can be followed by multiple coordinate pairs, + // which should be treated as linetos. + cmd = (cmd == 'm') ? 'l' : 'L'; + rargs = nsvg__getArgsPerElement(cmd); + cpx2 = cpx; + cpy2 = cpy; + initPoint = 1; + break; + case 'l': + case 'L': + nsvg__pathLineTo(p, &cpx, &cpy, args, cmd == 'l' ? 1 : 0); + cpx2 = cpx; + cpy2 = cpy; + break; + case 'H': + case 'h': + nsvg__pathHLineTo(p, &cpx, &cpy, args, cmd == 'h' ? 1 : 0); + cpx2 = cpx; + cpy2 = cpy; + break; + case 'V': + case 'v': + nsvg__pathVLineTo(p, &cpx, &cpy, args, cmd == 'v' ? 1 : 0); + cpx2 = cpx; + cpy2 = cpy; + break; + case 'C': + case 'c': + nsvg__pathCubicBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'c' ? 1 : 0); + break; + case 'S': + case 's': + nsvg__pathCubicBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 's' ? 1 : 0); + break; + case 'Q': + case 'q': + nsvg__pathQuadBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'q' ? 1 : 0); + break; + case 'T': + case 't': + nsvg__pathQuadBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 't' ? 1 : 0); + break; + case 'A': + case 'a': + nsvg__pathArcTo(p, &cpx, &cpy, args, cmd == 'a' ? 1 : 0); + cpx2 = cpx; + cpy2 = cpy; + break; + default: + if (nargs >= 2) { + cpx = args[nargs - 2]; + cpy = args[nargs - 1]; + cpx2 = cpx; + cpy2 = cpy; + } + break; + } + nargs = 0; + } + } + else { + cmd = item[0]; + if (cmd == 'M' || cmd == 'm') { + // Commit path. + if (p->npts > 0) + nsvg__addPath(p, closedFlag); + // Start new subpath. + nsvg__resetPath(p); + closedFlag = 0; + nargs = 0; + } + else if (initPoint == 0) { + // Do not allow other commands until initial point has been set (moveTo called once). + cmd = '\0'; + } + if (cmd == 'Z' || cmd == 'z') { + closedFlag = 1; + // Commit path. + if (p->npts > 0) { + // Move current point to first point + cpx = p->pts[0]; + cpy = p->pts[1]; + cpx2 = cpx; + cpy2 = cpy; + nsvg__addPath(p, closedFlag); + } + // Start new subpath. + nsvg__resetPath(p); + nsvg__moveTo(p, cpx, cpy); + closedFlag = 0; + nargs = 0; + } + rargs = nsvg__getArgsPerElement(cmd); + if (rargs == -1) { + // Command not recognized + cmd = '\0'; + rargs = 0; + } + } + } + // Commit path. + if (p->npts) + nsvg__addPath(p, closedFlag); + } + + nsvg__addShape(p); +} + +static void nsvg__parseRect(NSVGparser *p, const char **attr) +{ + float x = 0.0f; + float y = 0.0f; + float w = 0.0f; + float h = 0.0f; + float rx = -1.0f; // marks not set + float ry = -1.0f; + int i; + + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "x") == 0) + x = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); + if (strcmp(attr[i], "y") == 0) + y = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); + if (strcmp(attr[i], "width") == 0) + w = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, nsvg__actualWidth(p)); + if (strcmp(attr[i], "height") == 0) + h = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, nsvg__actualHeight(p)); + if (strcmp(attr[i], "rx") == 0) + rx = fabsf(nsvg__parseCoordinate(p, attr[i + 1], 0.0f, nsvg__actualWidth(p))); + if (strcmp(attr[i], "ry") == 0) + ry = fabsf(nsvg__parseCoordinate(p, attr[i + 1], 0.0f, nsvg__actualHeight(p))); + } + } + + if (rx < 0.0f && ry > 0.0f) + rx = ry; + if (ry < 0.0f && rx > 0.0f) + ry = rx; + if (rx < 0.0f) + rx = 0.0f; + if (ry < 0.0f) + ry = 0.0f; + if (rx > w / 2.0f) + rx = w / 2.0f; + if (ry > h / 2.0f) + ry = h / 2.0f; + + if (w != 0.0f && h != 0.0f) { + nsvg__resetPath(p); + + if (rx < 0.00001f || ry < 0.0001f) { + nsvg__moveTo(p, x, y); + nsvg__lineTo(p, x + w, y); + nsvg__lineTo(p, x + w, y + h); + nsvg__lineTo(p, x, y + h); + } + else { + // Rounded rectangle + nsvg__moveTo(p, x + rx, y); + nsvg__lineTo(p, x + w - rx, y); + nsvg__cubicBezTo(p, + x + w - rx * (1 - NSVG_KAPPA90), + y, + x + w, + y + ry * (1 - NSVG_KAPPA90), + x + w, + y + ry); + nsvg__lineTo(p, x + w, y + h - ry); + nsvg__cubicBezTo(p, + x + w, + y + h - ry * (1 - NSVG_KAPPA90), + x + w - rx * (1 - NSVG_KAPPA90), + y + h, + x + w - rx, + y + h); + nsvg__lineTo(p, x + rx, y + h); + nsvg__cubicBezTo(p, + x + rx * (1 - NSVG_KAPPA90), + y + h, + x, + y + h - ry * (1 - NSVG_KAPPA90), + x, + y + h - ry); + nsvg__lineTo(p, x, y + ry); + nsvg__cubicBezTo( + p, x, y + ry * (1 - NSVG_KAPPA90), x + rx * (1 - NSVG_KAPPA90), y, x + rx, y); + } + + nsvg__addPath(p, 1); + + nsvg__addShape(p); + } +} + +static void nsvg__parseCircle(NSVGparser *p, const char **attr) +{ + float cx = 0.0f; + float cy = 0.0f; + float r = 0.0f; + int i; + + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "cx") == 0) + cx = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); + if (strcmp(attr[i], "cy") == 0) + cy = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); + if (strcmp(attr[i], "r") == 0) + r = fabsf(nsvg__parseCoordinate(p, attr[i + 1], 0.0f, nsvg__actualLength(p))); + } + } + + if (r > 0.0f) { + nsvg__resetPath(p); + + nsvg__moveTo(p, cx + r, cy); + nsvg__cubicBezTo(p, cx + r, cy + r * NSVG_KAPPA90, cx + r * NSVG_KAPPA90, cy + r, cx, cy + r); + nsvg__cubicBezTo(p, cx - r * NSVG_KAPPA90, cy + r, cx - r, cy + r * NSVG_KAPPA90, cx - r, cy); + nsvg__cubicBezTo(p, cx - r, cy - r * NSVG_KAPPA90, cx - r * NSVG_KAPPA90, cy - r, cx, cy - r); + nsvg__cubicBezTo(p, cx + r * NSVG_KAPPA90, cy - r, cx + r, cy - r * NSVG_KAPPA90, cx + r, cy); + + nsvg__addPath(p, 1); + + nsvg__addShape(p); + } +} + +static void nsvg__parseEllipse(NSVGparser *p, const char **attr) +{ + float cx = 0.0f; + float cy = 0.0f; + float rx = 0.0f; + float ry = 0.0f; + int i; + + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "cx") == 0) + cx = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); + if (strcmp(attr[i], "cy") == 0) + cy = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); + if (strcmp(attr[i], "rx") == 0) + rx = fabsf(nsvg__parseCoordinate(p, attr[i + 1], 0.0f, nsvg__actualWidth(p))); + if (strcmp(attr[i], "ry") == 0) + ry = fabsf(nsvg__parseCoordinate(p, attr[i + 1], 0.0f, nsvg__actualHeight(p))); + } + } + + if (rx > 0.0f && ry > 0.0f) { + + nsvg__resetPath(p); + + nsvg__moveTo(p, cx + rx, cy); + nsvg__cubicBezTo( + p, cx + rx, cy + ry * NSVG_KAPPA90, cx + rx * NSVG_KAPPA90, cy + ry, cx, cy + ry); + nsvg__cubicBezTo( + p, cx - rx * NSVG_KAPPA90, cy + ry, cx - rx, cy + ry * NSVG_KAPPA90, cx - rx, cy); + nsvg__cubicBezTo( + p, cx - rx, cy - ry * NSVG_KAPPA90, cx - rx * NSVG_KAPPA90, cy - ry, cx, cy - ry); + nsvg__cubicBezTo( + p, cx + rx * NSVG_KAPPA90, cy - ry, cx + rx, cy - ry * NSVG_KAPPA90, cx + rx, cy); + + nsvg__addPath(p, 1); + + nsvg__addShape(p); + } +} + +static void nsvg__parseLine(NSVGparser *p, const char **attr) +{ + float x1 = 0.0; + float y1 = 0.0; + float x2 = 0.0; + float y2 = 0.0; + int i; + + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "x1") == 0) + x1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); + if (strcmp(attr[i], "y1") == 0) + y1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); + if (strcmp(attr[i], "x2") == 0) + x2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); + if (strcmp(attr[i], "y2") == 0) + y2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); + } + } + + nsvg__resetPath(p); + + nsvg__moveTo(p, x1, y1); + nsvg__lineTo(p, x2, y2); + + nsvg__addPath(p, 0); + + nsvg__addShape(p); +} + +static void nsvg__parsePoly(NSVGparser *p, const char **attr, int closeFlag) +{ + int i; + const char *s; + float args[2]; + int nargs, npts = 0; + char item[64]; + + nsvg__resetPath(p); + + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "points") == 0) { + s = attr[i + 1]; + nargs = 0; + while (*s) { + s = nsvg__getNextPathItem(s, item); + args[nargs++] = (float)nsvg__atof(item); + if (nargs >= 2) { + if (npts == 0) + nsvg__moveTo(p, args[0], args[1]); + else + nsvg__lineTo(p, args[0], args[1]); + nargs = 0; + npts++; + } + } + } + } + } + + nsvg__addPath(p, (char)closeFlag); + + nsvg__addShape(p); +} + +static void nsvg__parseSVG(NSVGparser *p, const char **attr) +{ + int i; + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "width") == 0) { + p->image->width = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 0.0f); + } + else if (strcmp(attr[i], "height") == 0) { + p->image->height = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 0.0f); + } + else if (strcmp(attr[i], "viewBox") == 0) { + const char *s = attr[i + 1]; + char buf[64]; + s = nsvg__parseNumber(s, buf, 64); + p->viewMinx = nsvg__atof(buf); + while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) + s++; + if (!*s) + return; + s = nsvg__parseNumber(s, buf, 64); + p->viewMiny = nsvg__atof(buf); + while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) + s++; + if (!*s) + return; + s = nsvg__parseNumber(s, buf, 64); + p->viewWidth = nsvg__atof(buf); + while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) + s++; + if (!*s) + return; + s = nsvg__parseNumber(s, buf, 64); + p->viewHeight = nsvg__atof(buf); + } + else if (strcmp(attr[i], "preserveAspectRatio") == 0) { + if (strstr(attr[i + 1], "none") != 0) { + // No uniform scaling + p->alignType = NSVG_ALIGN_NONE; + } + else { + // Parse X align + if (strstr(attr[i + 1], "xMin") != 0) + p->alignX = NSVG_ALIGN_MIN; + else if (strstr(attr[i + 1], "xMid") != 0) + p->alignX = NSVG_ALIGN_MID; + else if (strstr(attr[i + 1], "xMax") != 0) + p->alignX = NSVG_ALIGN_MAX; + // Parse X align + if (strstr(attr[i + 1], "yMin") != 0) + p->alignY = NSVG_ALIGN_MIN; + else if (strstr(attr[i + 1], "yMid") != 0) + p->alignY = NSVG_ALIGN_MID; + else if (strstr(attr[i + 1], "yMax") != 0) + p->alignY = NSVG_ALIGN_MAX; + // Parse meet/slice + p->alignType = NSVG_ALIGN_MEET; + if (strstr(attr[i + 1], "slice") != 0) + p->alignType = NSVG_ALIGN_SLICE; + } + } + } + } +} + +static void nsvg__parseGradient(NSVGparser *p, const char **attr, char type) +{ + int i; + NSVGgradientData *grad = (NSVGgradientData *)malloc(sizeof(NSVGgradientData)); + if (grad == NULL) + return; + memset(grad, 0, sizeof(NSVGgradientData)); + grad->units = NSVG_OBJECT_SPACE; + grad->type = type; + if (grad->type == NSVG_PAINT_LINEAR_GRADIENT) { + grad->linear.x1 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT); + grad->linear.y1 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT); + grad->linear.x2 = nsvg__coord(100.0f, NSVG_UNITS_PERCENT); + grad->linear.y2 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT); + } + else if (grad->type == NSVG_PAINT_RADIAL_GRADIENT) { + grad->radial.cx = nsvg__coord(50.0f, NSVG_UNITS_PERCENT); + grad->radial.cy = nsvg__coord(50.0f, NSVG_UNITS_PERCENT); + grad->radial.r = nsvg__coord(50.0f, NSVG_UNITS_PERCENT); + } + + nsvg__xformIdentity(grad->xform); + + for (i = 0; attr[i]; i += 2) { + if (strcmp(attr[i], "id") == 0) { + strncpy(grad->id, attr[i + 1], 63); + grad->id[63] = '\0'; + } + else if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "gradientUnits") == 0) { + if (strcmp(attr[i + 1], "objectBoundingBox") == 0) + grad->units = NSVG_OBJECT_SPACE; + else + grad->units = NSVG_USER_SPACE; + } + else if (strcmp(attr[i], "gradientTransform") == 0) { + nsvg__parseTransform(grad->xform, attr[i + 1]); + } + else if (strcmp(attr[i], "cx") == 0) { + grad->radial.cx = nsvg__parseCoordinateRaw(attr[i + 1]); + } + else if (strcmp(attr[i], "cy") == 0) { + grad->radial.cy = nsvg__parseCoordinateRaw(attr[i + 1]); + } + else if (strcmp(attr[i], "r") == 0) { + grad->radial.r = nsvg__parseCoordinateRaw(attr[i + 1]); + } + else if (strcmp(attr[i], "fx") == 0) { + grad->radial.fx = nsvg__parseCoordinateRaw(attr[i + 1]); + } + else if (strcmp(attr[i], "fy") == 0) { + grad->radial.fy = nsvg__parseCoordinateRaw(attr[i + 1]); + } + else if (strcmp(attr[i], "x1") == 0) { + grad->linear.x1 = nsvg__parseCoordinateRaw(attr[i + 1]); + } + else if (strcmp(attr[i], "y1") == 0) { + grad->linear.y1 = nsvg__parseCoordinateRaw(attr[i + 1]); + } + else if (strcmp(attr[i], "x2") == 0) { + grad->linear.x2 = nsvg__parseCoordinateRaw(attr[i + 1]); + } + else if (strcmp(attr[i], "y2") == 0) { + grad->linear.y2 = nsvg__parseCoordinateRaw(attr[i + 1]); + } + else if (strcmp(attr[i], "spreadMethod") == 0) { + if (strcmp(attr[i + 1], "pad") == 0) + grad->spread = NSVG_SPREAD_PAD; + else if (strcmp(attr[i + 1], "reflect") == 0) + grad->spread = NSVG_SPREAD_REFLECT; + else if (strcmp(attr[i + 1], "repeat") == 0) + grad->spread = NSVG_SPREAD_REPEAT; + } + else if (strcmp(attr[i], "xlink:href") == 0) { + const char *href = attr[i + 1]; + strncpy(grad->ref, href + 1, 62); + grad->ref[62] = '\0'; + } + } + } + + grad->next = p->gradients; + p->gradients = grad; +} + +static void nsvg__parseGradientStop(NSVGparser *p, const char **attr) +{ + NSVGattrib *curAttr = nsvg__getAttr(p); + NSVGgradientData *grad; + NSVGgradientStop *stop; + int i, idx; + + curAttr->stopOffset = 0; + curAttr->stopColor = 0; + curAttr->stopOpacity = 1.0f; + + for (i = 0; attr[i]; i += 2) { + nsvg__parseAttr(p, attr[i], attr[i + 1]); + } + + // Add stop to the last gradient. + grad = p->gradients; + if (grad == NULL) + return; + + grad->nstops++; + grad->stops = (NSVGgradientStop *)realloc(grad->stops, sizeof(NSVGgradientStop) * grad->nstops); + if (grad->stops == NULL) + return; + + // Insert + idx = grad->nstops - 1; + for (i = 0; i < grad->nstops - 1; i++) { + if (curAttr->stopOffset < grad->stops[i].offset) { + idx = i; + break; + } + } + if (idx != grad->nstops - 1) { + for (i = grad->nstops - 1; i > idx; i--) + grad->stops[i] = grad->stops[i - 1]; + } + + stop = &grad->stops[idx]; + stop->color = curAttr->stopColor; + stop->color |= (unsigned int)(curAttr->stopOpacity * 255) << 24; + stop->offset = curAttr->stopOffset; +} + +static void nsvg__startElement(void *ud, const char *el, const char **attr) +{ + NSVGparser *p = (NSVGparser *)ud; + + if (p->defsFlag) { + // Skip everything but gradients in defs + if (strcmp(el, "linearGradient") == 0) { + nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT); + } + else if (strcmp(el, "radialGradient") == 0) { + nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT); + } + else if (strcmp(el, "stop") == 0) { + nsvg__parseGradientStop(p, attr); + } + return; + } + + if (strcmp(el, "g") == 0) { + nsvg__pushAttr(p); + nsvg__parseAttribs(p, attr); + + /* Save the breadcrumb of groups. */ + if (p->breadcrumb_len < NSVG_MAX_BREADCRUMB) { + NSVGattrib *attr_id = nsvg__getAttr(p); + memcpy( + p->breadcrumb[p->breadcrumb_len], attr_id->id, sizeof(p->breadcrumb[p->breadcrumb_len])); + p->breadcrumb_len++; + } + } + else if (strcmp(el, "path") == 0) { + if (p->pathFlag) // Do not allow nested paths. + return; + nsvg__pushAttr(p); + nsvg__parsePath(p, attr); + nsvg__popAttr(p); + } + else if (strcmp(el, "rect") == 0) { + nsvg__pushAttr(p); + nsvg__parseRect(p, attr); + nsvg__popAttr(p); + } + else if (strcmp(el, "circle") == 0) { + nsvg__pushAttr(p); + nsvg__parseCircle(p, attr); + nsvg__popAttr(p); + } + else if (strcmp(el, "ellipse") == 0) { + nsvg__pushAttr(p); + nsvg__parseEllipse(p, attr); + nsvg__popAttr(p); + } + else if (strcmp(el, "line") == 0) { + nsvg__pushAttr(p); + nsvg__parseLine(p, attr); + nsvg__popAttr(p); + } + else if (strcmp(el, "polyline") == 0) { + nsvg__pushAttr(p); + nsvg__parsePoly(p, attr, 0); + nsvg__popAttr(p); + } + else if (strcmp(el, "polygon") == 0) { + nsvg__pushAttr(p); + nsvg__parsePoly(p, attr, 1); + nsvg__popAttr(p); + } + else if (strcmp(el, "linearGradient") == 0) { + nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT); + } + else if (strcmp(el, "radialGradient") == 0) { + nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT); + } + else if (strcmp(el, "stop") == 0) { + nsvg__parseGradientStop(p, attr); + } + else if (strcmp(el, "defs") == 0) { + p->defsFlag = 1; + } + else if (strcmp(el, "svg") == 0) { + nsvg__parseSVG(p, attr); + } +} + +static void nsvg__endElement(void *ud, const char *el) +{ + NSVGparser *p = (NSVGparser *)ud; + + if (strcmp(el, "g") == 0) { + /* Remove the breadcrumb level. */ + if (p->breadcrumb_len > 0) { + p->breadcrumb[p->breadcrumb_len - 1][0] = '\0'; + p->breadcrumb_len--; + } + + nsvg__popAttr(p); + } + else if (strcmp(el, "path") == 0) { + p->pathFlag = 0; + } + else if (strcmp(el, "defs") == 0) { + p->defsFlag = 0; + } +} + +static void nsvg__content(void *ud, const char *s) +{ + NSVG_NOTUSED(ud); + NSVG_NOTUSED(s); + // empty +} + +static void nsvg__imageBounds(NSVGparser *p, float *bounds) +{ + NSVGshape *shape; + shape = p->image->shapes; + if (shape == NULL) { + bounds[0] = bounds[1] = bounds[2] = bounds[3] = 0.0; + return; + } + bounds[0] = shape->bounds[0]; + bounds[1] = shape->bounds[1]; + bounds[2] = shape->bounds[2]; + bounds[3] = shape->bounds[3]; + for (shape = shape->next; shape != NULL; shape = shape->next) { + bounds[0] = nsvg__minf(bounds[0], shape->bounds[0]); + bounds[1] = nsvg__minf(bounds[1], shape->bounds[1]); + bounds[2] = nsvg__maxf(bounds[2], shape->bounds[2]); + bounds[3] = nsvg__maxf(bounds[3], shape->bounds[3]); + } +} + +static float nsvg__viewAlign(float content, float container, int type) +{ + if (type == NSVG_ALIGN_MIN) + return 0; + else if (type == NSVG_ALIGN_MAX) + return container - content; + // mid + return (container - content) * 0.5f; +} + +static void nsvg__scaleGradient(NSVGgradient *grad, float tx, float ty, float sx, float sy) +{ + float t[6]; + nsvg__xformSetTranslation(t, tx, ty); + nsvg__xformMultiply(grad->xform, t); + + nsvg__xformSetScale(t, sx, sy); + nsvg__xformMultiply(grad->xform, t); +} + +static void nsvg__scaleToViewbox(NSVGparser *p, const char *units) +{ + NSVGshape *shape; + NSVGpath *path; + float tx, ty, sx, sy, us, bounds[4], t[6], avgs; + int i; + float *pt; + + // Guess image size if not set completely. + nsvg__imageBounds(p, bounds); + + if (p->viewWidth == 0) { + if (p->image->width > 0) { + p->viewWidth = p->image->width; + } + else { + p->viewMinx = bounds[0]; + p->viewWidth = bounds[2] - bounds[0]; + } + } + if (p->viewHeight == 0) { + if (p->image->height > 0) { + p->viewHeight = p->image->height; + } + else { + p->viewMiny = bounds[1]; + p->viewHeight = bounds[3] - bounds[1]; + } + } + if (p->image->width == 0) + p->image->width = p->viewWidth; + if (p->image->height == 0) + p->image->height = p->viewHeight; + + tx = -p->viewMinx; + ty = -p->viewMiny; + sx = p->viewWidth > 0 ? p->image->width / p->viewWidth : 0; + sy = p->viewHeight > 0 ? p->image->height / p->viewHeight : 0; + // Unit scaling + us = 1.0f / nsvg__convertToPixels(p, nsvg__coord(1.0f, nsvg__parseUnits(units)), 0.0f, 1.0f); + + // Fix aspect ratio + if (p->alignType == NSVG_ALIGN_MEET) { + // fit whole image into viewbox + sx = sy = nsvg__minf(sx, sy); + tx += nsvg__viewAlign(p->viewWidth * sx, p->image->width, p->alignX) / sx; + ty += nsvg__viewAlign(p->viewHeight * sy, p->image->height, p->alignY) / sy; + } + else if (p->alignType == NSVG_ALIGN_SLICE) { + // fill whole viewbox with image + sx = sy = nsvg__maxf(sx, sy); + tx += nsvg__viewAlign(p->viewWidth * sx, p->image->width, p->alignX) / sx; + ty += nsvg__viewAlign(p->viewHeight * sy, p->image->height, p->alignY) / sy; + } + + // Transform + sx *= us; + sy *= us; + avgs = (sx + sy) / 2.0f; + for (shape = p->image->shapes; shape != NULL; shape = shape->next) { + shape->bounds[0] = (shape->bounds[0] + tx) * sx; + shape->bounds[1] = (shape->bounds[1] + ty) * sy; + shape->bounds[2] = (shape->bounds[2] + tx) * sx; + shape->bounds[3] = (shape->bounds[3] + ty) * sy; + for (path = shape->paths; path != NULL; path = path->next) { + path->bounds[0] = (path->bounds[0] + tx) * sx; + path->bounds[1] = (path->bounds[1] + ty) * sy; + path->bounds[2] = (path->bounds[2] + tx) * sx; + path->bounds[3] = (path->bounds[3] + ty) * sy; + for (i = 0; i < path->npts; i++) { + pt = &path->pts[i * 2]; + pt[0] = (pt[0] + tx) * sx; + pt[1] = (pt[1] + ty) * sy; + } + } + + if (shape->fill.type == NSVG_PAINT_LINEAR_GRADIENT || + shape->fill.type == NSVG_PAINT_RADIAL_GRADIENT) { + nsvg__scaleGradient(shape->fill.gradient, tx, ty, sx, sy); + memcpy(t, shape->fill.gradient->xform, sizeof(float) * 6); + nsvg__xformInverse(shape->fill.gradient->xform, t); + } + if (shape->stroke.type == NSVG_PAINT_LINEAR_GRADIENT || + shape->stroke.type == NSVG_PAINT_RADIAL_GRADIENT) { + nsvg__scaleGradient(shape->stroke.gradient, tx, ty, sx, sy); + memcpy(t, shape->stroke.gradient->xform, sizeof(float) * 6); + nsvg__xformInverse(shape->stroke.gradient->xform, t); + } + + shape->strokeWidth *= avgs; + shape->strokeDashOffset *= avgs; + for (i = 0; i < shape->strokeDashCount; i++) + shape->strokeDashArray[i] *= avgs; + } +} + +NSVGimage *nsvgParse(char *input, const char *units, float dpi) +{ + NSVGparser *p; + NSVGimage *ret = 0; + + p = nsvg__createParser(); + if (p == NULL) { + return NULL; + } + p->dpi = dpi; + + nsvg__parseXML(input, nsvg__startElement, nsvg__endElement, nsvg__content, p); + + // Scale to viewBox + nsvg__scaleToViewbox(p, units); + + ret = p->image; + p->image = NULL; + + nsvg__deleteParser(p); + + return ret; +} + +NSVGimage *nsvgParseFromFile(const char *filename, const char *units, float dpi) +{ + FILE *fp = NULL; + size_t size; + char *data = NULL; + NSVGimage *image = NULL; + + fp = fopen(filename, "rb"); + if (!fp) + goto error; + fseek(fp, 0, SEEK_END); + size = ftell(fp); + fseek(fp, 0, SEEK_SET); + data = (char *)malloc(size + 1); + if (data == NULL) + goto error; + if (fread(data, 1, size, fp) != size) + goto error; + data[size] = '\0'; // Must be null terminated. + fclose(fp); + image = nsvgParse(data, units, dpi); + free(data); + + return image; + +error: + if (fp) + fclose(fp); + if (data) + free(data); + if (image) + nsvgDelete(image); + return NULL; +} + +NSVGpath *nsvgDuplicatePath(NSVGpath *p) +{ + NSVGpath *res = NULL; + + if (p == NULL) + return NULL; + + res = (NSVGpath *)malloc(sizeof(NSVGpath)); + if (res == NULL) + goto error; + memset(res, 0, sizeof(NSVGpath)); + + res->pts = (float *)malloc(p->npts * 2 * sizeof(float)); + if (res->pts == NULL) + goto error; + memcpy(res->pts, p->pts, p->npts * sizeof(float) * 2); + res->npts = p->npts; + + memcpy(res->bounds, p->bounds, sizeof(p->bounds)); + + res->closed = p->closed; + + return res; + +error: + if (res != NULL) { + free(res->pts); + free(res); + } + return NULL; +} + +void nsvgDelete(NSVGimage *image) +{ + NSVGshape *snext, *shape; + if (image == NULL) + return; + shape = image->shapes; + while (shape != NULL) { + snext = shape->next; + nsvg__deletePaths(shape->paths); + nsvg__deletePaint(&shape->fill); + nsvg__deletePaint(&shape->stroke); + free(shape); + shape = snext; + } + free(image); +} + +#endif diff --git a/source/blender/python/intern/CMakeLists.txt b/source/blender/python/intern/CMakeLists.txt index c7816aed3c1..9ac8d4d9f47 100644 --- a/source/blender/python/intern/CMakeLists.txt +++ b/source/blender/python/intern/CMakeLists.txt @@ -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}") diff --git a/source/blender/python/intern/bpy_app_build_options.c b/source/blender/python/intern/bpy_app_build_options.c index 9f12c9f80f1..676d1b8045f 100644 --- a/source/blender/python/intern/bpy_app_build_options.c +++ b/source/blender/python/intern/bpy_app_build_options.c @@ -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; -- cgit v1.2.3 From a478d502dd2512a63657818e19ee2b74c1848bec Mon Sep 17 00:00:00 2001 From: Antonio Vazquez Date: Wed, 24 Mar 2021 16:01:44 +0100 Subject: GPencil: Fix unreported crash when apply Lattice modifier This error was produced because now it is possible to have several Lattice modifiers and the Bake was removing the lattice data of all modifiers. Now the data is only recalculated and removed for the current modifier. Also some cleanup of comments. --- .../gpencil_modifiers/intern/MOD_gpencillattice.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c b/source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c index 4efc1d9eaae..2934b89c747 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c @@ -136,36 +136,39 @@ static void bakeModifier(Main *UNUSED(bmain), bGPdata *gpd = ob->data; int oldframe = (int)DEG_get_ctime(depsgraph); - if (mmd->object == NULL) { + if ((mmd->object == NULL) || (mmd->object->type != OB_LATTICE)) { return; } LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { - /* apply lattice effects on this frame - * NOTE: this assumes that we don't want lattice animation on non-keyframed frames + /* Apply lattice effects on this frame + * NOTE: this assumes that we don't want lattice animation on non-keyframed frames. */ CFRA = gpf->framenum; BKE_scene_graph_update_for_newframe(depsgraph); - /* recalculate lattice data */ - BKE_gpencil_lattice_init(ob); + /* Recalculate lattice data. */ + if (mmd->cache_data) { + BKE_lattice_deform_data_destroy(mmd->cache_data); + } + mmd->cache_data = BKE_lattice_deform_data_create(mmd->object, ob); - /* compute lattice effects on this frame */ + /* Compute lattice effects on this frame. */ LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { deformStroke(md, depsgraph, ob, gpl, gpf, gps); } } } - /* free lingering data */ + /* Free lingering data. */ ldata = (struct LatticeDeformData *)mmd->cache_data; if (ldata) { BKE_lattice_deform_data_destroy(ldata); mmd->cache_data = NULL; } - /* return frame state and DB to original state */ + /* Return frame state and DB to original state. */ CFRA = oldframe; BKE_scene_graph_update_for_newframe(depsgraph); } -- cgit v1.2.3 From 80602936568b9756ff291172b8c3f7772f6ada07 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Wed, 24 Mar 2021 11:05:55 -0400 Subject: Fix T86875: "Show on Cage" crash for geometry nodes primitives Without `calc_object_remap` turned off in the conversion to and from BMesh for the primitive nodes, the `CD_ORIGINDEX` custom data layer has incorrect values. By using a different function to do the conversions, we can avoid this problem. Thanks to Jacques for finding the fix here. Differential Revision: https://developer.blender.org/D10805 --- source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc | 4 +++- source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc | 4 +++- .../nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc | 4 +++- .../blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc | 4 +++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc index 72b05dcce70..e9228a2942b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc @@ -223,8 +223,10 @@ Mesh *create_cylinder_or_cone_mesh(const float radius_top, transform.values, true); + BMeshToMeshParams params{}; + params.calc_object_remap = false; Mesh *mesh = (Mesh *)BKE_id_new_nomain(ID_ME, nullptr); - BM_mesh_bm_to_me_for_eval(bm, mesh, nullptr); + BM_mesh_bm_to_me(nullptr, bm, mesh, ¶ms); BM_mesh_free(bm); return mesh; diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc index 1803a13f651..f8a9bfd2ed1 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc @@ -50,8 +50,10 @@ static Mesh *create_cube_mesh(const float size) size, true); + BMeshToMeshParams params{}; + params.calc_object_remap = false; Mesh *mesh = (Mesh *)BKE_id_new_nomain(ID_ME, nullptr); - BM_mesh_bm_to_me_for_eval(bm, mesh, nullptr); + BM_mesh_bm_to_me(nullptr, bm, mesh, ¶ms); BM_mesh_free(bm); return mesh; diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc index f16b37fe977..242cc6ed7df 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc @@ -52,8 +52,10 @@ static Mesh *create_ico_sphere_mesh(const int subdivisions, const float radius) transform.values, true); + BMeshToMeshParams params{}; + params.calc_object_remap = false; Mesh *mesh = (Mesh *)BKE_id_new_nomain(ID_ME, nullptr); - BM_mesh_bm_to_me_for_eval(bm, mesh, nullptr); + BM_mesh_bm_to_me(nullptr, bm, mesh, ¶ms); BM_mesh_free(bm); return mesh; diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc index b7d249c18bc..8efba91da1a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc @@ -85,8 +85,10 @@ static Mesh *create_uv_sphere_mesh_bmesh(const float radius, const int segments, transform.values, true); + BMeshToMeshParams params{}; + params.calc_object_remap = false; Mesh *mesh = (Mesh *)BKE_id_new_nomain(ID_ME, nullptr); - BM_mesh_bm_to_me_for_eval(bm, mesh, nullptr); + BM_mesh_bm_to_me(nullptr, bm, mesh, ¶ms); BM_mesh_free(bm); return mesh; -- cgit v1.2.3 From 80450331c7cc015f471dbafc3c783fafd1707cf5 Mon Sep 17 00:00:00 2001 From: Sebastian Parborg Date: Wed, 24 Mar 2021 16:17:59 +0100 Subject: Fix T86884: Don't remove the "line art object add" enum item We would not add the "object line art" enum to the operator enum list, if there were no active object in the scene. This would make it impossible to call this operator from python code as the enum would we hidden when we were not in a viewport context. Always make the operator available, having no active object is not a strict requirement for the operator to work, so expose it always. --- source/blender/editors/object/object_add.c | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/source/blender/editors/object/object_add.c b/source/blender/editors/object/object_add.c index e4527740164..b3fb497aa9a 100644 --- a/source/blender/editors/object/object_add.c +++ b/source/blender/editors/object/object_add.c @@ -1440,7 +1440,7 @@ static int object_gpencil_add_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } -static const EnumPropertyItem *object_gpencil_add_options(bContext *C, +static const EnumPropertyItem *object_gpencil_add_options(bContext *UNUSED(C), PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *r_free) @@ -1453,18 +1453,9 @@ static const EnumPropertyItem *object_gpencil_add_options(bContext *C, /* Default types. */ for (i = 0; i < orig_count; i++) { - if (item_ref[i].value == GP_LRT_OBJECT || item_ref[i].value == GP_LRT_COLLECTION || - item_ref[i].value == GP_LRT_SCENE) { - if (item_ref[i].value == GP_LRT_SCENE) { - /* separator before line art types */ - RNA_enum_item_add_separator(&item, &totitem); - } - else if (item_ref[i].value == GP_LRT_OBJECT) { - Object *ob = CTX_data_active_object(C); - if (!ob || ob->type != OB_MESH) { - continue; - } - } + if (item_ref[i].value == GP_LRT_SCENE) { + /* separator before line art types */ + RNA_enum_item_add_separator(&item, &totitem); } RNA_enum_item_add(&item, &totitem, &item_ref[i]); } -- cgit v1.2.3 From bf0454b78bce7609b74423bd47d3d76fdadb077f Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Wed, 24 Mar 2021 11:33:30 -0400 Subject: Fix build error and warnings in new grease pencil IO code --- source/blender/io/gpencil/intern/gpencil_io_capi.cc | 4 +++- source/blender/io/gpencil/intern/gpencil_io_import_base.cc | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/source/blender/io/gpencil/intern/gpencil_io_capi.cc b/source/blender/io/gpencil/intern/gpencil_io_capi.cc index 34539a66fe8..231d23948ef 100644 --- a/source/blender/io/gpencil/intern/gpencil_io_capi.cc +++ b/source/blender/io/gpencil/intern/gpencil_io_capi.cc @@ -58,6 +58,7 @@ using blender::io::gpencil::GpencilExporterSVG; using blender::io::gpencil::GpencilImporterSVG; /* Check if frame is included. */ +#ifdef WITH_HARU static bool is_keyframe_included(bGPdata *gpd_, const int32_t framenum, const bool use_selected) { /* Check if exist a frame. */ @@ -75,6 +76,7 @@ static bool is_keyframe_included(bGPdata *gpd_, const int32_t framenum, const bo } return false; } +#endif /* Import frame. */ static bool gpencil_io_import_frame(void *in_importer, const GpencilIOParams &iparams) @@ -177,7 +179,7 @@ bool gpencil_io_export(const char *filename, GpencilIOParams *iparams) Scene *scene_ = CTX_data_scene(iparams->C); Object *ob = CTX_data_active_object(iparams->C); - UNUSED_VARS(depsgraph_, scene_, ob); + UNUSED_VARS(filename, depsgraph_, scene_, ob); switch (iparams->mode) { #ifdef WITH_PUGIXML diff --git a/source/blender/io/gpencil/intern/gpencil_io_import_base.cc b/source/blender/io/gpencil/intern/gpencil_io_import_base.cc index b49b4c969ad..2e7cfdeb5cd 100644 --- a/source/blender/io/gpencil/intern/gpencil_io_import_base.cc +++ b/source/blender/io/gpencil/intern/gpencil_io_import_base.cc @@ -35,8 +35,6 @@ #include "gpencil_io_import_base.h" -#include "pugixml.hpp" - namespace blender::io::gpencil { /* Constructor. */ -- cgit v1.2.3 From a6664383c6ef21ecaf03f0867ccef76732e12bf3 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Wed, 24 Mar 2021 16:35:48 +0100 Subject: Fix (unreported) Outliner: missing override tree items for collections in Viewlayer view. Probably lost at some point in recent refactor moving the whole tree building towards a more modular, C++ code. --- .../editors/space_outliner/tree/tree_display_view_layer.cc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/source/blender/editors/space_outliner/tree/tree_display_view_layer.cc b/source/blender/editors/space_outliner/tree/tree_display_view_layer.cc index 89c9960a24f..f00cf3c34c0 100644 --- a/source/blender/editors/space_outliner/tree/tree_display_view_layer.cc +++ b/source/blender/editors/space_outliner/tree/tree_display_view_layer.cc @@ -148,6 +148,14 @@ void TreeDisplayViewLayer::add_layer_collections_recursive(ListBase &tree, if (!exclude && show_objects_) { add_layer_collection_objects(ten->subtree, *lc, *ten); } + + const bool lib_overrides_visible = !SUPPORT_FILTER_OUTLINER(&space_outliner_) || + ((space_outliner_.filter & SO_FILTER_NO_LIB_OVERRIDE) == 0); + + if (lib_overrides_visible && ID_IS_OVERRIDE_LIBRARY_REAL(&lc->collection->id)) { + outliner_add_element( + &space_outliner_, &ten->subtree, &lc->collection->id, ten, TSE_LIBRARY_OVERRIDE_BASE, 0); + } } } -- cgit v1.2.3 From d0d0d9d7c659acba779dced73f003348dc173a9b Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Wed, 24 Mar 2021 17:01:19 +0100 Subject: Fix broken compilation after recent GPencil commit. rBa8a92cd15a52 was adding external libraries includes to `INC`, which is reserved to internal project includes. `INC` does not allow duplicates, and when using system libs both PugiXML and Haru headers are under the same path. --- source/blender/io/gpencil/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/blender/io/gpencil/CMakeLists.txt b/source/blender/io/gpencil/CMakeLists.txt index f394075cb9a..11c9affbe5a 100644 --- a/source/blender/io/gpencil/CMakeLists.txt +++ b/source/blender/io/gpencil/CMakeLists.txt @@ -68,7 +68,7 @@ if(WITH_PUGIXML) intern/gpencil_io_export_svg.h intern/gpencil_io_export_svg.cc ) - list(APPEND INC + list(APPEND INC_SYS ${PUGIXML_INCLUDE_DIR} ) list(APPEND LIB @@ -82,7 +82,7 @@ if(WITH_HARU) intern/gpencil_io_export_pdf.h intern/gpencil_io_export_pdf.cc ) - list(APPEND INC + list(APPEND INC_SYS ${HARU_INCLUDE_DIRS} ) list(APPEND LIB -- cgit v1.2.3 From a363d64b93cdebff92f2c84d4382d1227cf4425c Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Wed, 24 Mar 2021 12:36:19 -0400 Subject: UI: Use correct property split ratio for node socket buttons This makes the buttons drawn on nodes for unconnected string and color sockets draw with the correct 40% ratio for the label split rather than the old 50% ratio not used elsewhere in Blender anymore. The benefit is a cleaner look, because the button edges line up better with others, and a bit more space to type in attribute names. --- source/blender/editors/space_node/drawnode.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/blender/editors/space_node/drawnode.c b/source/blender/editors/space_node/drawnode.c index 1354c06305c..6864d34885a 100644 --- a/source/blender/editors/space_node/drawnode.c +++ b/source/blender/editors/space_node/drawnode.c @@ -3429,13 +3429,13 @@ static void std_node_socket_draw( } break; case SOCK_RGBA: { - uiLayout *row = uiLayoutSplit(layout, 0.5f, false); + uiLayout *row = uiLayoutSplit(layout, 0.4f, false); uiItemL(row, text, 0); uiItemR(row, ptr, "default_value", DEFAULT_FLAGS, "", 0); break; } case SOCK_STRING: { - uiLayout *row = uiLayoutSplit(layout, 0.5f, false); + uiLayout *row = uiLayoutSplit(layout, 0.4f, false); uiItemL(row, text, 0); const bNodeTree *node_tree = (const bNodeTree *)node_ptr->owner_id; -- cgit v1.2.3 From 9ad3d1d36b64f336fabdd9b3d9f02b13740068b0 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Wed, 24 Mar 2021 12:44:48 -0400 Subject: Cleanup: Remove unecessary enum funcs The separator can be added directly in the enum items rather than in a callback. Differential Revision: https://developer.blender.org/D10806 --- source/blender/editors/object/object_add.c | 27 --------------------------- source/blender/makesrna/intern/rna_object.c | 1 + 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/source/blender/editors/object/object_add.c b/source/blender/editors/object/object_add.c index b3fb497aa9a..50dc1af5ca8 100644 --- a/source/blender/editors/object/object_add.c +++ b/source/blender/editors/object/object_add.c @@ -1440,32 +1440,6 @@ static int object_gpencil_add_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } -static const EnumPropertyItem *object_gpencil_add_options(bContext *UNUSED(C), - PointerRNA *UNUSED(ptr), - PropertyRNA *UNUSED(prop), - bool *r_free) -{ - EnumPropertyItem *item = NULL; - const EnumPropertyItem *item_ref = rna_enum_object_gpencil_type_items; - int totitem = 0; - int i = 0; - int orig_count = RNA_enum_items_count(item_ref); - - /* Default types. */ - for (i = 0; i < orig_count; i++) { - if (item_ref[i].value == GP_LRT_SCENE) { - /* separator before line art types */ - RNA_enum_item_add_separator(&item, &totitem); - } - RNA_enum_item_add(&item, &totitem, &item_ref[i]); - } - - RNA_enum_item_end(&item, &totitem); - *r_free = true; - - return item; -} - void OBJECT_OT_gpencil_add(wmOperatorType *ot) { /* identifiers */ @@ -1486,7 +1460,6 @@ void OBJECT_OT_gpencil_add(wmOperatorType *ot) ED_object_add_generic_props(ot, false); ot->prop = RNA_def_enum(ot->srna, "type", rna_enum_object_gpencil_type_items, 0, "Type", ""); - RNA_def_enum_funcs(ot->prop, object_gpencil_add_options); } /** \} */ diff --git a/source/blender/makesrna/intern/rna_object.c b/source/blender/makesrna/intern/rna_object.c index b04fea11a4d..b53318cfbfe 100644 --- a/source/blender/makesrna/intern/rna_object.c +++ b/source/blender/makesrna/intern/rna_object.c @@ -160,6 +160,7 @@ const EnumPropertyItem rna_enum_object_gpencil_type_items[] = { {GP_EMPTY, "EMPTY", ICON_EMPTY_AXIS, "Blank", "Create an empty grease pencil object"}, {GP_STROKE, "STROKE", ICON_STROKE, "Stroke", "Create a simple stroke with basic colors"}, {GP_MONKEY, "MONKEY", ICON_MONKEY, "Monkey", "Construct a Suzanne grease pencil object"}, + {0, "", 0, NULL, NULL}, {GP_LRT_SCENE, "LRT_SCENE", ICON_SCENE_DATA, -- cgit v1.2.3